Full Code of songquanpeng/one-api for AI

main 8df4a2670b98 cached
533 files
1.8 MB
496.8k tokens
1460 symbols
1 requests
Download .txt
Showing preview only (1,944K chars total). Download the full file or copy to clipboard to get everything.
Repository: songquanpeng/one-api
Branch: main
Commit: 8df4a2670b98
Files: 533
Total size: 1.8 MB

Directory structure:
gitextract_g67kz3s3/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   └── workflows/
│       ├── ci.yml
│       ├── docker-image.yml
│       ├── linux-release.yml
│       ├── macos-release.yml
│       └── windows-release.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.en.md
├── README.ja.md
├── README.md
├── VERSION
├── bin/
│   ├── migration_v0.2-v0.3.sql
│   ├── migration_v0.3-v0.4.sql
│   └── time_test.sh
├── common/
│   ├── blacklist/
│   │   └── main.go
│   ├── client/
│   │   └── init.go
│   ├── config/
│   │   └── config.go
│   ├── constants.go
│   ├── conv/
│   │   └── any.go
│   ├── crypto.go
│   ├── ctxkey/
│   │   └── key.go
│   ├── custom-event.go
│   ├── database.go
│   ├── embed-file-system.go
│   ├── env/
│   │   └── helper.go
│   ├── gin.go
│   ├── helper/
│   │   ├── helper.go
│   │   ├── key.go
│   │   └── time.go
│   ├── i18n/
│   │   ├── i18n.go
│   │   └── locales/
│   │       ├── en.json
│   │       └── zh-CN.json
│   ├── image/
│   │   ├── image.go
│   │   └── image_test.go
│   ├── init.go
│   ├── logger/
│   │   ├── constants.go
│   │   └── logger.go
│   ├── message/
│   │   ├── email.go
│   │   ├── main.go
│   │   ├── message-pusher.go
│   │   └── template.go
│   ├── network/
│   │   ├── ip.go
│   │   └── ip_test.go
│   ├── random/
│   │   └── main.go
│   ├── rate-limit.go
│   ├── redis.go
│   ├── render/
│   │   └── render.go
│   ├── utils/
│   │   └── array.go
│   ├── utils.go
│   ├── validate.go
│   └── verification.go
├── controller/
│   ├── auth/
│   │   ├── github.go
│   │   ├── lark.go
│   │   ├── oidc.go
│   │   └── wechat.go
│   ├── billing.go
│   ├── channel-billing.go
│   ├── channel-test.go
│   ├── channel.go
│   ├── group.go
│   ├── log.go
│   ├── misc.go
│   ├── model.go
│   ├── option.go
│   ├── redemption.go
│   ├── relay.go
│   ├── token.go
│   └── user.go
├── docker-compose.yml
├── docs/
│   └── API.md
├── go.mod
├── go.sum
├── main.go
├── middleware/
│   ├── auth.go
│   ├── cache.go
│   ├── cors.go
│   ├── distributor.go
│   ├── gzip.go
│   ├── language.go
│   ├── logger.go
│   ├── rate-limit.go
│   ├── recover.go
│   ├── request-id.go
│   ├── turnstile-check.go
│   └── utils.go
├── model/
│   ├── ability.go
│   ├── cache.go
│   ├── channel.go
│   ├── log.go
│   ├── main.go
│   ├── option.go
│   ├── redemption.go
│   ├── token.go
│   ├── user.go
│   └── utils.go
├── monitor/
│   ├── channel.go
│   ├── manage.go
│   └── metric.go
├── one-api.service
├── pull_request_template.md
├── relay/
│   ├── adaptor/
│   │   ├── ai360/
│   │   │   └── constants.go
│   │   ├── aiproxy/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── ali/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── image.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── alibailian/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── anthropic/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── aws/
│   │   │   ├── adaptor.go
│   │   │   ├── claude/
│   │   │   │   ├── adapter.go
│   │   │   │   ├── main.go
│   │   │   │   └── model.go
│   │   │   ├── llama3/
│   │   │   │   ├── adapter.go
│   │   │   │   ├── main.go
│   │   │   │   ├── main_test.go
│   │   │   │   └── model.go
│   │   │   ├── registry.go
│   │   │   └── utils/
│   │   │       ├── adaptor.go
│   │   │       └── utils.go
│   │   ├── baichuan/
│   │   │   └── constants.go
│   │   ├── baidu/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── baiduv2/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── cloudflare/
│   │   │   ├── adaptor.go
│   │   │   ├── constant.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── cohere/
│   │   │   ├── adaptor.go
│   │   │   ├── constant.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── common.go
│   │   ├── coze/
│   │   │   ├── adaptor.go
│   │   │   ├── constant/
│   │   │   │   ├── contenttype/
│   │   │   │   │   └── define.go
│   │   │   │   ├── event/
│   │   │   │   │   └── define.go
│   │   │   │   └── messagetype/
│   │   │   │       └── define.go
│   │   │   ├── constants.go
│   │   │   ├── helper.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── deepl/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── helper.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── deepseek/
│   │   │   └── constants.go
│   │   ├── doubao/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── gemini/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── geminiv2/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── groq/
│   │   │   └── constants.go
│   │   ├── interface.go
│   │   ├── lingyiwanwu/
│   │   │   └── constants.go
│   │   ├── minimax/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── mistral/
│   │   │   └── constants.go
│   │   ├── moonshot/
│   │   │   └── constants.go
│   │   ├── novita/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── ollama/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── openai/
│   │   │   ├── adaptor.go
│   │   │   ├── compatible.go
│   │   │   ├── constants.go
│   │   │   ├── helper.go
│   │   │   ├── image.go
│   │   │   ├── main.go
│   │   │   ├── model.go
│   │   │   ├── token.go
│   │   │   └── util.go
│   │   ├── openrouter/
│   │   │   └── constants.go
│   │   ├── palm/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── model.go
│   │   │   └── palm.go
│   │   ├── proxy/
│   │   │   └── adaptor.go
│   │   ├── replicate/
│   │   │   ├── adaptor.go
│   │   │   ├── chat.go
│   │   │   ├── constant.go
│   │   │   ├── image.go
│   │   │   └── model.go
│   │   ├── siliconflow/
│   │   │   └── constants.go
│   │   ├── stepfun/
│   │   │   └── constants.go
│   │   ├── tencent/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── togetherai/
│   │   │   └── constants.go
│   │   ├── vertexai/
│   │   │   ├── adaptor.go
│   │   │   ├── claude/
│   │   │   │   ├── adapter.go
│   │   │   │   └── model.go
│   │   │   ├── gemini/
│   │   │   │   └── adapter.go
│   │   │   ├── registry.go
│   │   │   └── token.go
│   │   ├── xai/
│   │   │   └── constants.go
│   │   ├── xunfei/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── domain.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── xunfeiv2/
│   │   │   └── constants.go
│   │   └── zhipu/
│   │       ├── adaptor.go
│   │       ├── constants.go
│   │       ├── main.go
│   │       └── model.go
│   ├── adaptor.go
│   ├── adaptor_test.go
│   ├── apitype/
│   │   └── define.go
│   ├── billing/
│   │   ├── billing.go
│   │   └── ratio/
│   │       ├── group.go
│   │       ├── image.go
│   │       └── model.go
│   ├── channeltype/
│   │   ├── define.go
│   │   ├── helper.go
│   │   ├── url.go
│   │   └── url_test.go
│   ├── constant/
│   │   ├── common.go
│   │   ├── finishreason/
│   │   │   └── define.go
│   │   └── role/
│   │       └── define.go
│   ├── controller/
│   │   ├── audio.go
│   │   ├── error.go
│   │   ├── helper.go
│   │   ├── image.go
│   │   ├── proxy.go
│   │   ├── text.go
│   │   └── validator/
│   │       └── validation.go
│   ├── meta/
│   │   └── relay_meta.go
│   ├── model/
│   │   ├── constant.go
│   │   ├── general.go
│   │   ├── image.go
│   │   ├── message.go
│   │   ├── misc.go
│   │   └── tool.go
│   └── relaymode/
│       ├── define.go
│       └── helper.go
├── router/
│   ├── api.go
│   ├── dashboard.go
│   ├── main.go
│   ├── relay.go
│   └── web.go
└── web/
    ├── README.md
    ├── THEMES
    ├── air/
    │   ├── .gitignore
    │   ├── README.md
    │   ├── package.json
    │   ├── public/
    │   │   ├── index.html
    │   │   └── robots.txt
    │   ├── src/
    │   │   ├── App.js
    │   │   ├── components/
    │   │   │   ├── ChannelsTable.js
    │   │   │   ├── Footer.js
    │   │   │   ├── GitHubOAuth.js
    │   │   │   ├── HeaderBar.js
    │   │   │   ├── Loading.js
    │   │   │   ├── LoginForm.js
    │   │   │   ├── LogsTable.js
    │   │   │   ├── MjLogsTable.js
    │   │   │   ├── OperationSetting.js
    │   │   │   ├── OtherSetting.js
    │   │   │   ├── PasswordResetConfirm.js
    │   │   │   ├── PasswordResetForm.js
    │   │   │   ├── PersonalSetting.js
    │   │   │   ├── PrivateRoute.js
    │   │   │   ├── RedemptionsTable.js
    │   │   │   ├── RegisterForm.js
    │   │   │   ├── SiderBar.js
    │   │   │   ├── SystemSetting.js
    │   │   │   ├── TokensTable.js
    │   │   │   ├── UsersTable.js
    │   │   │   ├── WeChatIcon.js
    │   │   │   └── utils.js
    │   │   ├── constants/
    │   │   │   ├── channel.constants.js
    │   │   │   ├── common.constant.js
    │   │   │   ├── index.js
    │   │   │   ├── toast.constants.js
    │   │   │   └── user.constants.js
    │   │   ├── context/
    │   │   │   ├── Status/
    │   │   │   │   ├── index.js
    │   │   │   │   └── reducer.js
    │   │   │   └── User/
    │   │   │       ├── index.js
    │   │   │       └── reducer.js
    │   │   ├── helpers/
    │   │   │   ├── api.js
    │   │   │   ├── auth-header.js
    │   │   │   ├── history.js
    │   │   │   ├── index.js
    │   │   │   ├── render.js
    │   │   │   └── utils.js
    │   │   ├── index.css
    │   │   ├── index.js
    │   │   └── pages/
    │   │       ├── About/
    │   │       │   └── index.js
    │   │       ├── Channel/
    │   │       │   ├── EditChannel.js
    │   │       │   └── index.js
    │   │       ├── Chat/
    │   │       │   └── index.js
    │   │       ├── Detail/
    │   │       │   └── index.js
    │   │       ├── Home/
    │   │       │   └── index.js
    │   │       ├── Log/
    │   │       │   └── index.js
    │   │       ├── Midjourney/
    │   │       │   └── index.js
    │   │       ├── NotFound/
    │   │       │   └── index.js
    │   │       ├── Redemption/
    │   │       │   ├── EditRedemption.js
    │   │       │   └── index.js
    │   │       ├── Setting/
    │   │       │   └── index.js
    │   │       ├── Token/
    │   │       │   ├── EditToken.js
    │   │       │   └── index.js
    │   │       ├── TopUp/
    │   │       │   └── index.js
    │   │       └── User/
    │   │           ├── AddUser.js
    │   │           ├── EditUser.js
    │   │           └── index.js
    │   └── vercel.json
    ├── berry/
    │   ├── .gitignore
    │   ├── .prettierrc
    │   ├── README.md
    │   ├── jsconfig.json
    │   ├── package.json
    │   ├── public/
    │   │   └── index.html
    │   └── src/
    │       ├── App.js
    │       ├── assets/
    │       │   └── scss/
    │       │       ├── _themes-vars.module.scss
    │       │       ├── fonts.scss
    │       │       └── style.scss
    │       ├── config.js
    │       ├── constants/
    │       │   ├── ChannelConstants.js
    │       │   ├── CommonConstants.js
    │       │   ├── SnackbarConstants.js
    │       │   └── index.js
    │       ├── contexts/
    │       │   ├── StatusContext.js
    │       │   └── UserContext.js
    │       ├── hooks/
    │       │   ├── useAuth.js
    │       │   ├── useLogin.js
    │       │   ├── useRegister.js
    │       │   └── useScriptRef.js
    │       ├── index.js
    │       ├── layout/
    │       │   ├── MainLayout/
    │       │   │   ├── Header/
    │       │   │   │   ├── ProfileSection/
    │       │   │   │   │   └── index.js
    │       │   │   │   └── index.js
    │       │   │   ├── LogoSection/
    │       │   │   │   └── index.js
    │       │   │   ├── Sidebar/
    │       │   │   │   ├── MenuCard/
    │       │   │   │   │   └── index.js
    │       │   │   │   ├── MenuList/
    │       │   │   │   │   ├── NavCollapse/
    │       │   │   │   │   │   └── index.js
    │       │   │   │   │   ├── NavGroup/
    │       │   │   │   │   │   └── index.js
    │       │   │   │   │   ├── NavItem/
    │       │   │   │   │   │   └── index.js
    │       │   │   │   │   └── index.js
    │       │   │   │   └── index.js
    │       │   │   └── index.js
    │       │   ├── MinimalLayout/
    │       │   │   ├── Header/
    │       │   │   │   └── index.js
    │       │   │   └── index.js
    │       │   ├── NavMotion.js
    │       │   └── NavigationScroll.js
    │       ├── menu-items/
    │       │   ├── index.js
    │       │   └── panel.js
    │       ├── routes/
    │       │   ├── MainRoutes.js
    │       │   ├── OtherRoutes.js
    │       │   └── index.js
    │       ├── serviceWorker.js
    │       ├── store/
    │       │   ├── accountReducer.js
    │       │   ├── actions.js
    │       │   ├── constant.js
    │       │   ├── customizationReducer.js
    │       │   ├── index.js
    │       │   ├── reducer.js
    │       │   └── siteInfoReducer.js
    │       ├── themes/
    │       │   ├── compStyleOverride.js
    │       │   ├── index.js
    │       │   ├── palette.js
    │       │   └── typography.js
    │       ├── ui-component/
    │       │   ├── AdminContainer.js
    │       │   ├── Footer.js
    │       │   ├── Label.js
    │       │   ├── Loadable.js
    │       │   ├── Loader.js
    │       │   ├── Logo.js
    │       │   ├── SvgColor.js
    │       │   ├── Switch.js
    │       │   ├── TableToolBar.js
    │       │   ├── ThemeButton.js
    │       │   ├── cards/
    │       │   │   ├── CardSecondaryAction.js
    │       │   │   ├── MainCard.js
    │       │   │   ├── Skeleton/
    │       │   │   │   ├── EarningCard.js
    │       │   │   │   ├── ImagePlaceholder.js
    │       │   │   │   ├── PopularCard.js
    │       │   │   │   ├── ProductPlaceholder.js
    │       │   │   │   ├── TotalGrowthBarChart.js
    │       │   │   │   └── TotalIncomeCard.js
    │       │   │   ├── SubCard.js
    │       │   │   └── UserCard.js
    │       │   └── extended/
    │       │       ├── AnimateButton.js
    │       │       ├── Avatar.js
    │       │       ├── Breadcrumbs.js
    │       │       └── Transitions.js
    │       ├── utils/
    │       │   ├── api.js
    │       │   ├── chart.js
    │       │   ├── common.js
    │       │   ├── password-strength.js
    │       │   └── route-guard/
    │       │       └── AuthGuard.js
    │       └── views/
    │           ├── About/
    │           │   └── index.js
    │           ├── Authentication/
    │           │   ├── Auth/
    │           │   │   ├── ForgetPassword.js
    │           │   │   ├── GitHubOAuth.js
    │           │   │   ├── LarkOAuth.js
    │           │   │   ├── Login.js
    │           │   │   ├── OidcOAuth.js
    │           │   │   ├── Register.js
    │           │   │   └── ResetPassword.js
    │           │   ├── AuthCardWrapper.js
    │           │   ├── AuthForms/
    │           │   │   ├── AuthLogin.js
    │           │   │   ├── AuthRegister.js
    │           │   │   ├── ForgetPasswordForm.js
    │           │   │   ├── ResetPasswordForm.js
    │           │   │   └── WechatModal.js
    │           │   └── AuthWrapper.js
    │           ├── Channel/
    │           │   ├── component/
    │           │   │   ├── EditModal.js
    │           │   │   ├── GroupLabel.js
    │           │   │   ├── NameLabel.js
    │           │   │   ├── ResponseTimeLabel.js
    │           │   │   ├── TableHead.js
    │           │   │   └── TableRow.js
    │           │   ├── index.js
    │           │   └── type/
    │           │       └── Config.js
    │           ├── Dashboard/
    │           │   ├── component/
    │           │   │   ├── StatisticalBarChart.js
    │           │   │   ├── StatisticalCard.js
    │           │   │   └── StatisticalLineChartCard.js
    │           │   └── index.js
    │           ├── Error/
    │           │   └── index.js
    │           ├── Home/
    │           │   ├── baseIndex.js
    │           │   └── index.js
    │           ├── Log/
    │           │   ├── component/
    │           │   │   ├── TableHead.js
    │           │   │   ├── TableRow.js
    │           │   │   └── TableToolBar.js
    │           │   ├── index.js
    │           │   └── type/
    │           │       └── LogType.js
    │           ├── Profile/
    │           │   ├── component/
    │           │   │   └── EmailModal.js
    │           │   └── index.js
    │           ├── Redemption/
    │           │   ├── component/
    │           │   │   ├── EditModal.js
    │           │   │   ├── TableHead.js
    │           │   │   └── TableRow.js
    │           │   └── index.js
    │           ├── Setting/
    │           │   ├── component/
    │           │   │   ├── OperationSetting.js
    │           │   │   ├── OtherSetting.js
    │           │   │   └── SystemSetting.js
    │           │   └── index.js
    │           ├── Token/
    │           │   ├── component/
    │           │   │   ├── EditModal.js
    │           │   │   ├── TableHead.js
    │           │   │   └── TableRow.js
    │           │   └── index.js
    │           ├── Topup/
    │           │   ├── component/
    │           │   │   ├── InviteCard.js
    │           │   │   └── TopupCard.js
    │           │   └── index.js
    │           └── User/
    │               ├── component/
    │               │   ├── EditModal.js
    │               │   ├── TableHead.js
    │               │   └── TableRow.js
    │               └── index.js
    ├── build.sh
    └── default/
        ├── .gitignore
        ├── README.md
        ├── package.json
        ├── public/
        │   ├── index.html
        │   └── robots.txt
        ├── src/
        │   ├── App.js
        │   ├── components/
        │   │   ├── ChannelsTable.js
        │   │   ├── Footer.js
        │   │   ├── GitHubOAuth.js
        │   │   ├── Header.js
        │   │   ├── LarkOAuth.js
        │   │   ├── Loading.js
        │   │   ├── LoginForm.js
        │   │   ├── LogsTable.js
        │   │   ├── OperationSetting.js
        │   │   ├── OtherSetting.js
        │   │   ├── PasswordResetConfirm.js
        │   │   ├── PasswordResetForm.js
        │   │   ├── PersonalSetting.js
        │   │   ├── PrivateRoute.js
        │   │   ├── RedemptionsTable.js
        │   │   ├── RegisterForm.js
        │   │   ├── SystemSetting.js
        │   │   ├── TokensTable.js
        │   │   ├── UsersTable.js
        │   │   └── utils.js
        │   ├── constants/
        │   │   ├── channel.constants.js
        │   │   ├── common.constant.js
        │   │   ├── index.js
        │   │   ├── toast.constants.js
        │   │   └── user.constants.js
        │   ├── context/
        │   │   ├── Status/
        │   │   │   ├── index.js
        │   │   │   └── reducer.js
        │   │   └── User/
        │   │       ├── index.js
        │   │       └── reducer.js
        │   ├── helpers/
        │   │   ├── api.js
        │   │   ├── auth-header.js
        │   │   ├── helper.js
        │   │   ├── history.js
        │   │   ├── index.js
        │   │   ├── render.js
        │   │   └── utils.js
        │   ├── i18n.js
        │   ├── index.css
        │   ├── index.js
        │   ├── locales/
        │   │   ├── en/
        │   │   │   └── translation.json
        │   │   └── zh/
        │   │       └── translation.json
        │   └── pages/
        │       ├── About/
        │       │   └── index.js
        │       ├── Channel/
        │       │   ├── EditChannel.js
        │       │   └── index.js
        │       ├── Chat/
        │       │   └── index.js
        │       ├── Dashboard/
        │       │   ├── Dashboard.css
        │       │   └── index.js
        │       ├── Home/
        │       │   └── index.js
        │       ├── Log/
        │       │   └── index.js
        │       ├── NotFound/
        │       │   └── index.js
        │       ├── Redemption/
        │       │   ├── EditRedemption.js
        │       │   └── index.js
        │       ├── Setting/
        │       │   └── index.js
        │       ├── Token/
        │       │   ├── EditToken.js
        │       │   └── index.js
        │       ├── TopUp/
        │       │   └── index.js
        │       └── User/
        │           ├── AddUser.js
        │           ├── EditUser.js
        │           └── index.js
        └── vercel.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
custom: ['https://iamazing.cn/page/reward']

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: 报告问题
about: 使用简练详细的语言描述你遇到的问题
title: ''
labels: bug
assignees: ''

---

**例行检查**

[//]: # (方框内删除已有的空格,填 x 号)
+ [ ] 我已确认目前没有类似 issue
+ [ ] 我已确认我已升级到最新版本
+ [ ] 我已完整查看过项目 README,尤其是常见问题部分
+ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈 
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**

**问题描述**

**复现步骤**

**预期结果**

**相关截图**
如果没有的话,请删除此节。

================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: 项目群聊
    url: https://openai.justsong.cn/
    about: QQ 群:828520184,自动审核,备注 One API
  - name: 赞赏支持
    url: https://iamazing.cn/page/reward
    about: 请作者喝杯咖啡,以激励作者持续开发


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: 功能请求
about: 使用简练详细的语言描述希望加入的新功能
title: ''
labels: enhancement
assignees: ''

---

**例行检查**

[//]: # (方框内删除已有的空格,填 x 号)
+ [ ] 我已确认目前没有类似 issue
+ [ ] 我已确认我已升级到最新版本
+ [ ] 我已完整查看过项目 README,已确定现有版本无法满足需求
+ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**

**功能描述**

**应用场景**


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

# This setup assumes that you run the unit tests with code coverage in the same
# workflow that will also print the coverage report as comment to the pull request.
# Therefore, you need to trigger this workflow when a pull request is (re)opened or
# when new code is pushed to the branch of the pull request. In addition, you also
# need to trigger this workflow when new code is pushed to the main branch because
# we need to upload the code coverage results as artifact for the main branch as
# well since it will be the baseline code coverage.
#
# We do not want to trigger the workflow for pushes to *any* branch because this
# would trigger our jobs twice on pull requests (once from "push" event and once
# from "pull_request->synchronize")
on:
  push:
    branches:
      - 'main'

jobs:
  unit_tests:
    name: "Unit tests"
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v4
        with:
          go-version: ^1.22

      # When you execute your unit tests, make sure to use the "-coverprofile" flag to write a
      # coverage profile to a file. You will need the name of the file (e.g. "coverage.txt")
      # in the next step as well as the next job.
      - name: Test
        run: go test -cover -coverprofile=coverage.txt ./...
      - uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  commit_lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: wagoid/commitlint-github-action@v6


================================================
FILE: .github/workflows/docker-image.yml
================================================
name: Publish Docker image

on:
  push:
    tags:
      - 'v*.*.*'
  workflow_dispatch:
    inputs:
      name:
        description: 'reason'
        required: false
jobs:
  push_to_registries:
    name: Push Docker image to multiple registries
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
    steps:
      - name: Check out the repo
        uses: actions/checkout@v3

      - name: Check repository URL
        run: |
          REPO_URL=$(git config --get remote.origin.url)
          if [[ $REPO_URL == *"pro" ]]; then
            exit 1
          fi

      - name: Save version info
        run: |
          git describe --tags > VERSION 

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Log in to the Container registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: |
            ${{ contains(github.ref, 'alpha') && 'justsong/one-api-alpha' || 'justsong/one-api' }}
            ${{ contains(github.ref, 'alpha') && format('ghcr.io/{0}-alpha', github.repository) || format('ghcr.io/{0}', github.repository) }}

      - name: Build and push Docker images
        uses: docker/build-push-action@v3
        with:
          context: .
          platforms: ${{ contains(github.ref, 'alpha') && 'linux/amd64' || 'linux/amd64' }}
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

================================================
FILE: .github/workflows/linux-release.yml
================================================
name: Linux Release
permissions:
  contents: write

on:
  push:
    tags:
      - 'v*.*.*'
      - '!*-alpha*'
      - '!*-preview*'
  workflow_dispatch:
    inputs:
      name:
        description: 'reason'
        required: false
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Check repository URL
        run: |
          REPO_URL=$(git config --get remote.origin.url)
          if [[ $REPO_URL == *"pro" ]]; then
            exit 1
          fi
      - uses: actions/setup-node@v3
        with:
          node-version: 16
      - name: Build Frontend
        env:
          CI: ""
        run: |
          cd web
          git describe --tags > VERSION
          REACT_APP_VERSION=$(git describe --tags) chmod u+x ./build.sh && ./build.sh
          cd ..
      - name: Set up Go
        uses: actions/setup-go@v3
        with:
          go-version: '>=1.18.0'
      - name: Build Backend (amd64)
        run: |
          go mod download
          go build -ldflags "-s -w -X 'github.com/songquanpeng/one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api

      - name: Build Backend (arm64)
        run: |
          sudo apt-get update
          sudo apt-get install gcc-aarch64-linux-gnu
          CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api-arm64

      - name: Release
        uses: softprops/action-gh-release@v1
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: |
            one-api
            one-api-arm64
          draft: true
          generate_release_notes: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

================================================
FILE: .github/workflows/macos-release.yml
================================================
name: macOS Release
permissions:
  contents: write

on:
  push:
    tags:
      - 'v*.*.*'
      - '!*-alpha*'
      - '!*-preview*'
  workflow_dispatch:
    inputs:
      name:
        description: 'reason'
        required: false
jobs:
  release:
    runs-on: macos-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Check repository URL
        run: |
          REPO_URL=$(git config --get remote.origin.url)
          if [[ $REPO_URL == *"pro" ]]; then
            exit 1
          fi
      - uses: actions/setup-node@v3
        with:
          node-version: 16
      - name: Build Frontend
        env:
          CI: ""
        run: |
          cd web
          git describe --tags > VERSION
          REACT_APP_VERSION=$(git describe --tags) chmod u+x ./build.sh && ./build.sh
          cd ..
      - name: Set up Go
        uses: actions/setup-go@v3
        with:
          go-version: '>=1.18.0'
      - name: Build Backend
        run: |
          go mod download
          go build -ldflags "-X 'github.com/songquanpeng/one-api/common.Version=$(git describe --tags)'" -o one-api-macos
      - name: Release
        uses: softprops/action-gh-release@v1
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: one-api-macos
          draft: true
          generate_release_notes: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/windows-release.yml
================================================
name: Windows Release
permissions:
  contents: write

on:
  push:
    tags:
      - 'v*.*.*'
      - '!*-alpha*'
      - '!*-preview*'
  workflow_dispatch:
    inputs:
      name:
        description: 'reason'
        required: false
jobs:
  release:
    runs-on: windows-latest
    defaults:
      run:
        shell: bash
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Check repository URL
        run: |
          REPO_URL=$(git config --get remote.origin.url)
          if [[ $REPO_URL == *"pro" ]]; then
            exit 1
          fi
      - uses: actions/setup-node@v3
        with:
          node-version: 16
      - name: Build Frontend
        env:
          CI: ""
        run: |
          cd web/default
          npm install
          REACT_APP_VERSION=$(git describe --tags) npm run build
          cd ../..
      - name: Set up Go
        uses: actions/setup-go@v3
        with:
          go-version: '>=1.18.0'
      - name: Build Backend
        run: |
          go mod download
          go build -ldflags "-s -w -X 'github.com/songquanpeng/one-api/common.Version=$(git describe --tags)'" -o one-api.exe
      - name: Release
        uses: softprops/action-gh-release@v1
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: one-api.exe
          draft: true
          generate_release_notes: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

================================================
FILE: .gitignore
================================================
.idea
.vscode
upload
*.exe
*.db
build
*.db-journal
logs
data
/web/node_modules
cmd.md
.env
/one-api
temp
.DS_Store

================================================
FILE: Dockerfile
================================================
FROM --platform=$BUILDPLATFORM node:16 AS builder

WORKDIR /web
COPY ./VERSION .
COPY ./web .

RUN npm install --prefix /web/default & \
    npm install --prefix /web/berry & \
    npm install --prefix /web/air & \
    wait

RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat ./VERSION) npm run build --prefix /web/default & \
    DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat ./VERSION) npm run build --prefix /web/berry & \
    DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat ./VERSION) npm run build --prefix /web/air & \
    wait

FROM golang:alpine AS builder2

RUN apk add --no-cache \
    gcc \
    musl-dev \
    sqlite-dev \
    build-base

ENV GO111MODULE=on \
    CGO_ENABLED=1 \
    GOOS=linux

WORKDIR /build

ADD go.mod go.sum ./
RUN go mod download

COPY . .
COPY --from=builder /web/build ./web/build

RUN go build -trimpath -ldflags "-s -w -X 'github.com/songquanpeng/one-api/common.Version=$(cat VERSION)' -linkmode external -extldflags '-static'" -o one-api

FROM alpine:latest

RUN apk add --no-cache ca-certificates tzdata

COPY --from=builder2 /build/one-api /

EXPOSE 3000
WORKDIR /data
ENTRYPOINT ["/one-api"]

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 JustSong

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.en.md
================================================
<p align="right">
    <a href="./README.md">中文</a> | <strong>English</strong> | <a href="./README.ja.md">日本語</a>
</p>

<p align="center">
  <a href="https://github.com/songquanpeng/one-api"><img src="https://raw.githubusercontent.com/songquanpeng/one-api/main/web/default/public/logo.png" width="150" height="150" alt="one-api logo"></a>
</p>

<div align="center">

# One API

_✨ Access all LLM through the standard OpenAI API format, easy to deploy & use ✨_

</div>

<p align="center">
  <a href="https://raw.githubusercontent.com/songquanpeng/one-api/main/LICENSE">
    <img src="https://img.shields.io/github/license/songquanpeng/one-api?color=brightgreen" alt="license">
  </a>
  <a href="https://github.com/songquanpeng/one-api/releases/latest">
    <img src="https://img.shields.io/github/v/release/songquanpeng/one-api?color=brightgreen&include_prereleases" alt="release">
  </a>
  <a href="https://hub.docker.com/repository/docker/justsong/one-api">
    <img src="https://img.shields.io/docker/pulls/justsong/one-api?color=brightgreen" alt="docker pull">
  </a>
  <a href="https://github.com/songquanpeng/one-api/releases/latest">
    <img src="https://img.shields.io/github/downloads/songquanpeng/one-api/total?color=brightgreen&include_prereleases" alt="release">
  </a>
  <a href="https://goreportcard.com/report/github.com/songquanpeng/one-api">
    <img src="https://goreportcard.com/badge/github.com/songquanpeng/one-api" alt="GoReportCard">
  </a>
</p>

<p align="center">
  <a href="#deployment">Deployment Tutorial</a>
  ·
  <a href="#usage">Usage</a>
  ·
  <a href="https://github.com/songquanpeng/one-api/issues">Feedback</a>
  ·
  <a href="#screenshots">Screenshots</a>
  ·
  <a href="https://openai.justsong.cn/">Live Demo</a>
  ·
  <a href="#faq">FAQ</a>
  ·
  <a href="#related-projects">Related Projects</a>
  ·
  <a href="https://iamazing.cn/page/reward">Donate</a>
</p>

> **Warning**: This README is translated by ChatGPT. Please feel free to submit a PR if you find any translation errors.

> **Note**: The latest image pulled from Docker may be an `alpha` release. Specify the version manually if you require stability.

## Features
1. Support for multiple large models:
   + [x] [OpenAI ChatGPT Series Models](https://platform.openai.com/docs/guides/gpt/chat-completions-api) (Supports [Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference))
   + [x] [Anthropic Claude Series Models](https://anthropic.com)
   + [x] [Google PaLM2 and Gemini Series Models](https://developers.generativeai.google)
   + [x] [Baidu Wenxin Yiyuan Series Models](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html)
   + [x] [Alibaba Tongyi Qianwen Series Models](https://help.aliyun.com/document_detail/2400395.html)
   + [x] [Zhipu ChatGLM Series Models](https://bigmodel.cn)
2. Supports access to multiple channels through **load balancing**.
3. Supports **stream mode** that enables typewriter-like effect through stream transmission.
4. Supports **multi-machine deployment**. [See here](#multi-machine-deployment) for more details.
5. Supports **token management** that allows setting token expiration time and usage count.
6. Supports **voucher management** that enables batch generation and export of vouchers. Vouchers can be used for account balance replenishment.
7. Supports **channel management** that allows bulk creation of channels.
8. Supports **user grouping** and **channel grouping** for setting different rates for different groups.
9. Supports channel **model list configuration**.
10. Supports **quota details checking**.
11. Supports **user invite rewards**.
12. Allows display of balance in USD.
13. Supports announcement publishing, recharge link setting, and initial balance setting for new users.
14. Offers rich **customization** options:
    1. Supports customization of system name, logo, and footer.
    2. Supports customization of homepage and about page using HTML & Markdown code, or embedding a standalone webpage through iframe.
15. Supports management API access through system access tokens.
16. Supports Cloudflare Turnstile user verification.
17. Supports user management and multiple user login/registration methods:
    + Email login/registration and password reset via email.
    + [GitHub OAuth](https://github.com/settings/applications/new).
    + WeChat Official Account authorization (requires additional deployment of [WeChat Server](https://github.com/songquanpeng/wechat-server)).
18. Immediate support and encapsulation of other major model APIs as they become available.

## Deployment
### Docker Deployment

Deployment command:
`docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api`

Update command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR`

The first `3000` in `-p 3000:3000` is the port of the host, which can be modified as needed.

Data will be saved in the `/home/ubuntu/data/one-api` directory on the host. Ensure that the directory exists and has write permissions, or change it to a suitable directory.

Nginx reference configuration:
```
server{
   server_name openai.justsong.cn;  # Modify your domain name accordingly

   location / {
          client_max_body_size  64m;
          proxy_http_version 1.1;
          proxy_pass http://localhost:3000;  # Modify your port accordingly
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $remote_addr;
          proxy_cache_bypass $http_upgrade;
          proxy_set_header Accept-Encoding gzip;
   }
}
```

Next, configure HTTPS with Let's Encrypt certbot:
```bash
# Install certbot on Ubuntu:
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# Generate certificates & modify Nginx configuration
sudo certbot --nginx
# Follow the prompts
# Restart Nginx
sudo service nginx restart
```

The initial account username is `root` and password is `123456`.

### Manual Deployment
1. Download the executable file from [GitHub Releases](https://github.com/songquanpeng/one-api/releases/latest) or compile from source:
   ```shell
   git clone https://github.com/songquanpeng/one-api.git

   # Build the frontend
   cd one-api/web/default
   npm install
   npm run build

   # Build the backend
   cd ../..
   go mod download
   go build -ldflags "-s -w" -o one-api
   ```
2. Run:
   ```shell
   chmod u+x one-api
   ./one-api --port 3000 --log-dir ./logs
   ```
3. Access [http://localhost:3000/](http://localhost:3000/) and log in. The initial account username is `root` and password is `123456`.

For more detailed deployment tutorials, please refer to [this page](https://iamazing.cn/page/how-to-deploy-a-website).

### Multi-machine Deployment
1. Set the same `SESSION_SECRET` for all servers.
2. Set `SQL_DSN` and use MySQL instead of SQLite. All servers should connect to the same database.
3. Set the `NODE_TYPE` for all non-master nodes to `slave`.
4. Set `SYNC_FREQUENCY` for servers to periodically sync configurations from the database.
5. Non-master nodes can optionally set `FRONTEND_BASE_URL` to redirect page requests to the master server.
6. Install Redis separately on non-master nodes, and configure `REDIS_CONN_STRING` so that the database can be accessed with zero latency when the cache has not expired.
7. If the main server also has high latency accessing the database, Redis must be enabled and `SYNC_FREQUENCY` must be set to periodically sync configurations from the database.

Please refer to the [environment variables](#environment-variables) section for details on using environment variables.

### Deployment on Control Panels (e.g., Baota)
Refer to [#175](https://github.com/songquanpeng/one-api/issues/175) for detailed instructions.

If you encounter a blank page after deployment, refer to [#97](https://github.com/songquanpeng/one-api/issues/97) for possible solutions.

### Deployment on Third-Party Platforms
<details>
<summary><strong>Deploy on Sealos</strong></summary>
<div>

> Sealos supports high concurrency, dynamic scaling, and stable operations for millions of users.

> Click the button below to deploy with one click.👇

[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy?templateName=one-api)


</div>
</details>

<details>
<summary><strong>Deployment on Zeabur</strong></summary>
<div>

> Zeabur's servers are located overseas, automatically solving network issues, and the free quota is sufficient for personal usage.

[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/7Q0KO3)

1. First, fork the code.
2. Go to [Zeabur](https://zeabur.com?referralCode=songquanpeng), log in, and enter the console.
3. Create a new project. In Service -> Add Service, select Marketplace, and choose MySQL. Note down the connection parameters (username, password, address, and port).
4. Copy the connection parameters and run ```create database `one-api` ``` to create the database.
5. Then, in Service -> Add Service, select Git (authorization is required for the first use) and choose your forked repository.
6. Automatic deployment will start, but please cancel it for now. Go to the Variable tab, add a `PORT` with a value of `3000`, and then add a `SQL_DSN` with a value of `<username>:<password>@tcp(<addr>:<port>)/one-api`. Save the changes. Please note that if `SQL_DSN` is not set, data will not be persisted, and the data will be lost after redeployment.
7. Select Redeploy.
8. In the Domains tab, select a suitable domain name prefix, such as "my-one-api". The final domain name will be "my-one-api.zeabur.app". You can also CNAME your own domain name.
9. Wait for the deployment to complete, and click on the generated domain name to access One API.

</div>
</details>

## Configuration
The system is ready to use out of the box.

You can configure it by setting environment variables or command line parameters.

After the system starts, log in as the `root` user to further configure the system.

## Usage
Add your API Key on the `Channels` page, and then add an access token on the `Tokens` page.

You can then use your access token to access One API. The usage is consistent with the [OpenAI API](https://platform.openai.com/docs/api-reference/introduction).

In places where the OpenAI API is used, remember to set the API Base to your One API deployment address, for example: `https://openai.justsong.cn`. The API Key should be the token generated in One API.

Note that the specific API Base format depends on the client you are using.

```mermaid
graph LR
    A(User)
    A --->|Request| B(One API)
    B -->|Relay Request| C(OpenAI)
    B -->|Relay Request| D(Azure)
    B -->|Relay Request| E(Other downstream channels)
```

To specify which channel to use for the current request, you can add the channel ID after the token, for example: `Authorization: Bearer ONE_API_KEY-CHANNEL_ID`.
Note that the token needs to be created by an administrator to specify the channel ID.

If the channel ID is not provided, load balancing will be used to distribute the requests to multiple channels.

### Environment Variables
1. `REDIS_CONN_STRING`: When set, Redis will be used as the storage for request rate limiting instead of memory.
    + Example: `REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
2. `SESSION_SECRET`: When set, a fixed session key will be used to ensure that cookies of logged-in users are still valid after the system restarts.
    + Example: `SESSION_SECRET=random_string`
3. `SQL_DSN`: When set, the specified database will be used instead of SQLite. Please use MySQL version 8.0.
    + Example: `SQL_DSN=root:123456@tcp(localhost:3306)/oneapi`
4. `LOG_SQL_DSN`: When set, a separate database will be used for the `logs` table; please use MySQL or PostgreSQL.
    + Example: `LOG_SQL_DSN=root:123456@tcp(localhost:3306)/oneapi-logs`
5. `FRONTEND_BASE_URL`: When set, the specified frontend address will be used instead of the backend address.
    + Example: `FRONTEND_BASE_URL=https://openai.justsong.cn`
6. 'MEMORY_CACHE_ENABLED': Enabling memory caching can cause a certain delay in updating user quotas, with optional values of 'true' and 'false'. If not set, it defaults to 'false'.
7. `SYNC_FREQUENCY`: When set, the system will periodically sync configurations from the database, with the unit in seconds. If not set, no sync will happen.
    + Example: `SYNC_FREQUENCY=60`
8. `NODE_TYPE`: When set, specifies the node type. Valid values are `master` and `slave`. If not set, it defaults to `master`.
    + Example: `NODE_TYPE=slave`
9. `CHANNEL_UPDATE_FREQUENCY`: When set, it periodically updates the channel balances, with the unit in minutes. If not set, no update will happen.
    + Example: `CHANNEL_UPDATE_FREQUENCY=1440`
10. `CHANNEL_TEST_FREQUENCY`: When set, it periodically tests the channels, with the unit in minutes. If not set, no test will happen.
    + Example: `CHANNEL_TEST_FREQUENCY=1440`
11. `POLLING_INTERVAL`: The time interval (in seconds) between requests when updating channel balances and testing channel availability. Default is no interval.
    + Example: `POLLING_INTERVAL=5`
12. `BATCH_UPDATE_ENABLED`: Enabling batch database update aggregation can cause a certain delay in updating user quotas. The optional values are 'true' and 'false', but if not set, it defaults to 'false'.
    +Example: ` BATCH_UPDATE_ENABLED=true`
    +If you encounter an issue with too many database connections, you can try enabling this option.
13. `BATCH_UPDATE_INTERVAL=5`: The time interval for batch updating aggregates, measured in seconds, defaults to '5'.
    +Example: ` BATCH_UPDATE_INTERVAL=5`
14. Request frequency limit:
    + `GLOBAL_API_RATE_LIMIT`: Global API rate limit (excluding relay requests), the maximum number of requests within three minutes per IP, default to 180.
    + `GLOBAL_WEL_RATE_LIMIT`: Global web speed limit, the maximum number of requests within three minutes per IP, default to 60.
15. Encoder cache settings:
    +`TIKTOKEN_CACHE_DIR`: By default, when the program starts, it will download the encoding of some common word elements online, such as' gpt-3.5 turbo '. In some unstable network environments or offline situations, it may cause startup problems. This directory can be configured to cache data and can be migrated to an offline environment.
    +`DATA_GYM_CACHE_DIR`: Currently, this configuration has the same function as' TIKTOKEN-CACHE-DIR ', but its priority is not as high as it.
16. `RELAY_TIMEOUT`: Relay timeout setting, measured in seconds, with no default timeout time set.
17. `RELAY_PROXY`: After setting up, use this proxy to request APIs.
18. `USER_CONTENT_REQUEST_TIMEOUT`: The timeout period for users to upload and download content, measured in seconds.
19. `USER_CONTENT_REQUEST_PROXY`: After setting up, use this agent to request content uploaded by users, such as images.
20. `SQLITE_BUSY_TIMEOUT`: SQLite lock wait timeout setting, measured in milliseconds, default to '3000'.
21. `GEMINI_SAFETY_SETTING`: Gemini's security settings are set to 'BLOCK-NONE' by default.
22. `GEMINI_VERSION`: The Gemini version used by the One API, which defaults to 'v1'.
23. `THE`: The system's theme setting, default to 'default', specific optional values refer to [here] (./web/README. md).
24. `ENABLE_METRIC`: Whether to disable channels based on request success rate, default not enabled, optional values are 'true' and 'false'.
25. `METRIC_QUEUE_SIZE`: Request success rate statistics queue size, default to '10'.
26. `METRIC_SUCCESS_RATE_THRESHOLD`: Request success rate threshold, default to '0.8'.
27. `INITIAL_ROOT_TOKEN`: If this value is set, a root user token with the value of the environment variable will be automatically created when the system starts for the first time.
28. `INITIAL_ROOT_ACCESS_TOKEN`: If this value is set, a system management token will be automatically created for the root user with a value of the environment variable when the system starts for the first time.

### Command Line Parameters
1. `--port <port_number>`: Specifies the port number on which the server listens. Defaults to `3000`.
    + Example: `--port 3000`
2. `--log-dir <log_dir>`: Specifies the log directory. If not set, the logs will not be saved.
    + Example: `--log-dir ./logs`
3. `--version`: Prints the system version number and exits.
4. `--help`: Displays the command usage help and parameter descriptions.

## Screenshots
![channel](https://user-images.githubusercontent.com/39998050/233837954-ae6683aa-5c4f-429f-a949-6645a83c9490.png)
![token](https://user-images.githubusercontent.com/39998050/233837971-dab488b7-6d96-43af-b640-a168e8d1c9bf.png)

## FAQ
1. What is quota? How is it calculated? Does One API have quota calculation issues?
    + Quota = Group multiplier * Model multiplier * (number of prompt tokens + number of completion tokens * completion multiplier)
    + The completion multiplier is fixed at 1.33 for GPT3.5 and 2 for GPT4, consistent with the official definition.
    + If it is not a stream mode, the official API will return the total number of tokens consumed. However, please note that the consumption multipliers for prompts and completions are different.
2. Why does it prompt "insufficient quota" even though my account balance is sufficient?
    + Please check if your token quota is sufficient. It is separate from the account balance.
    + The token quota is used to set the maximum usage and can be freely set by the user.
3. It says "No available channels" when trying to use a channel. What should I do?
    + Please check the user and channel group settings.
    + Also check the channel model settings.
4. Channel testing reports an error: "invalid character '<' looking for beginning of value"
    + This error occurs when the returned value is not valid JSON but an HTML page.
    + Most likely, the IP of your deployment site or the node of the proxy has been blocked by CloudFlare.
5. ChatGPT Next Web reports an error: "Failed to fetch"
    + Do not set `BASE_URL` during deployment.
    + Double-check that your interface address and API Key are correct.

## Related Projects
* [FastGPT](https://github.com/labring/FastGPT): Knowledge question answering system based on the LLM
* [VChart](https://github.com/VisActor/VChart):  More than just a cross-platform charting library, but also an expressive data storyteller.
* [VMind](https://github.com/VisActor/VMind):  Not just automatic, but also fantastic. Open-source solution for intelligent visualization.
* * [CherryStudio](https://github.com/CherryHQ/cherry-studio):  A cross-platform AI client that integrates multiple service providers and supports local knowledge base management.

## Note
This project is an open-source project. Please use it in compliance with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**. It must not be used for illegal purposes.

This project is released under the MIT license. Based on this, attribution and a link to this project must be included at the bottom of the page.

The same applies to derivative projects based on this project.

If you do not wish to include attribution, prior authorization must be obtained.

According to the MIT license, users should bear the risk and responsibility of using this project, and the developer of this open-source project is not responsible for this.


================================================
FILE: README.ja.md
================================================
<p align="right">
    <a href="./README.md">中文</a> | <a href="./README.en.md">English</a> | <strong>日本語</strong>
</p>

<p align="center">
  <a href="https://github.com/songquanpeng/one-api"><img src="https://raw.githubusercontent.com/songquanpeng/one-api/main/web/default/public/logo.png" width="150" height="150" alt="one-api logo"></a>
</p>

<div align="center">

# One API

_✨ 標準的な OpenAI API フォーマットを通じてすべての LLM にアクセスでき、導入と利用が容易です ✨_

</div>

<p align="center">
  <a href="https://raw.githubusercontent.com/songquanpeng/one-api/main/LICENSE">
    <img src="https://img.shields.io/github/license/songquanpeng/one-api?color=brightgreen" alt="license">
  </a>
  <a href="https://github.com/songquanpeng/one-api/releases/latest">
    <img src="https://img.shields.io/github/v/release/songquanpeng/one-api?color=brightgreen&include_prereleases" alt="release">
  </a>
  <a href="https://hub.docker.com/repository/docker/justsong/one-api">
    <img src="https://img.shields.io/docker/pulls/justsong/one-api?color=brightgreen" alt="docker pull">
  </a>
  <a href="https://github.com/songquanpeng/one-api/releases/latest">
    <img src="https://img.shields.io/github/downloads/songquanpeng/one-api/total?color=brightgreen&include_prereleases" alt="release">
  </a>
  <a href="https://goreportcard.com/report/github.com/songquanpeng/one-api">
    <img src="https://goreportcard.com/badge/github.com/songquanpeng/one-api" alt="GoReportCard">
  </a>
</p>

<p align="center">
  <a href="#deployment">デプロイチュートリアル</a>
  ·
  <a href="#usage">使用方法</a>
  ·
  <a href="https://github.com/songquanpeng/one-api/issues">フィードバック</a>
  ·
  <a href="#screenshots">スクリーンショット</a>
  ·
  <a href="https://openai.justsong.cn/">ライブデモ</a>
  ·
  <a href="#faq">FAQ</a>
  ·
  <a href="#related-projects">関連プロジェクト</a>
  ·
  <a href="https://iamazing.cn/page/reward">寄付</a>
</p>

> **警告**: この README は ChatGPT によって翻訳されています。翻訳ミスを発見した場合は遠慮なく PR を投稿してください。

> **注**: Docker からプルされた最新のイメージは、`alpha` リリースかもしれません。安定性が必要な場合は、手動でバージョンを指定してください。

## 特徴
1. 複数の大型モデルをサポート:
   + [x] [OpenAI ChatGPT シリーズモデル](https://platform.openai.com/docs/guides/gpt/chat-completions-api) ([Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference) をサポート)
   + [x] [Anthropic Claude シリーズモデル](https://anthropic.com)
   + [x] [Google PaLM2/Gemini シリーズモデル](https://developers.generativeai.google)
   + [x] [Baidu Wenxin Yiyuan シリーズモデル](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html)
   + [x] [Alibaba Tongyi Qianwen シリーズモデル](https://help.aliyun.com/document_detail/2400395.html)
   + [x] [Zhipu ChatGLM シリーズモデル](https://bigmodel.cn)
2. **ロードバランシング**による複数チャンネルへのアクセスをサポート。
3. ストリーム伝送によるタイプライター的効果を可能にする**ストリームモード**に対応。
4. **マルチマシンデプロイ**に対応。[詳細はこちら](#multi-machine-deployment)を参照。
5. トークンの有効期限や使用回数を設定できる**トークン管理**に対応しています。
6. **バウチャー管理**に対応しており、バウチャーの一括生成やエクスポートが可能です。バウチャーは口座残高の補充に利用できます。
7. **チャンネル管理**に対応し、チャンネルの一括作成が可能。
8. グループごとに異なるレートを設定するための**ユーザーグループ**と**チャンネルグループ**をサポートしています。
9. チャンネル**モデルリスト設定**に対応。
10. **クォータ詳細チェック**をサポート。
11. **ユーザー招待報酬**をサポートします。
12. 米ドルでの残高表示が可能。
13. 新規ユーザー向けのお知らせ公開、リチャージリンク設定、初期残高設定に対応。
14. 豊富な**カスタマイズ**オプションを提供します:
    1. システム名、ロゴ、フッターのカスタマイズが可能。
    2. HTML と Markdown コードを使用したホームページとアバウトページのカスタマイズ、または iframe を介したスタンドアロンウェブページの埋め込みをサポートしています。
15. システム・アクセストークンによる管理 API アクセスをサポートする。
16. Cloudflare Turnstile によるユーザー認証に対応。
17. ユーザー管理と複数のユーザーログイン/登録方法をサポート:
    + 電子メールによるログイン/登録とパスワードリセット。
    + [GitHub OAuth](https://github.com/settings/applications/new)。
    + WeChat 公式アカウントの認証([WeChat Server](https://github.com/songquanpeng/wechat-server)の追加導入が必要)。
18. 他の主要なモデル API が利用可能になった場合、即座にサポートし、カプセル化する。

## デプロイメント
### Docker デプロイメント

デプロイコマンド:
`docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api`。

コマンドを更新する: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrr/watchtower -cR`。

`-p 3000:3000` の最初の `3000` はホストのポートで、必要に応じて変更できます。

データはホストの `/home/ubuntu/data/one-api` ディレクトリに保存される。このディレクトリが存在し、書き込み権限があることを確認する、もしくは適切なディレクトリに変更してください。

Nginxリファレンス設定:
```
server{
   server_name openai.justsong.cn;  # ドメイン名は適宜変更

   location / {
          client_max_body_size  64m;
          proxy_http_version 1.1;
          proxy_pass http://localhost:3000;  # それに応じてポートを変更
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $remote_addr;
          proxy_cache_bypass $http_upgrade;
          proxy_set_header Accept-Encoding gzip;
          proxy_read_timeout 300s;  # GPT-4 はより長いタイムアウトが必要
   }
}
```

次に、Let's Encrypt certbot を使って HTTPS を設定します:
```bash
# Ubuntu に certbot をインストール:
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# 証明書の生成と Nginx 設定の変更
sudo certbot --nginx
# プロンプトに従う
# Nginx を再起動
sudo service nginx restart
```

初期アカウントのユーザー名は `root` で、パスワードは `123456` です。

### マニュアルデプロイ
1. [GitHub Releases](https://github.com/songquanpeng/one-api/releases/latest) から実行ファイルをダウンロードする、もしくはソースからコンパイルする:
   ```shell
   git clone https://github.com/songquanpeng/one-api.git

   # フロントエンドのビルド
   cd one-api/web/default
   npm install
   npm run build

   # バックエンドのビルド
   cd ../..
   go mod download
   go build -ldflags "-s -w" -o one-api
   ```
2. 実行:
   ```shell
   chmod u+x one-api
   ./one-api --port 3000 --log-dir ./logs
   ```
3. [http://localhost:3000/](http://localhost:3000/) にアクセスし、ログインする。初期アカウントのユーザー名は `root`、パスワードは `123456` である。

より詳細なデプロイのチュートリアルについては、[このページ](https://iamazing.cn/page/how-to-deploy-a-website) を参照してください。

### マルチマシンデプロイ
1. すべてのサーバに同じ `SESSION_SECRET` を設定する。
2. `SQL_DSN` を設定し、SQLite の代わりに MySQL を使用する。すべてのサーバは同じデータベースに接続する。
3. マスターノード以外のノードの `NODE_TYPE` を `slave` に設定する。
4. データベースから定期的に設定を同期するサーバーには `SYNC_FREQUENCY` を設定する。
5. マスター以外のノードでは、オプションで `FRONTEND_BASE_URL` を設定して、ページ要求をマスターサーバーにリダイレクトすることができます。
6. マスター以外のノードには Redis を個別にインストールし、`REDIS_CONN_STRING` を設定して、キャッシュの有効期限が切れていないときにデータベースにゼロレイテンシーでアクセスできるようにする。
7. メインサーバーでもデータベースへのアクセスが高レイテンシになる場合は、Redis を有効にし、`SYNC_FREQUENCY` を設定してデータベースから定期的に設定を同期する必要がある。

Please refer to the [environment variables](#environment-variables) section for details on using environment variables.

### コントロールパネル(例: Baota)への展開
詳しい手順は [#175](https://github.com/songquanpeng/one-api/issues/175) を参照してください。

配置後に空白のページが表示される場合は、[#97](https://github.com/songquanpeng/one-api/issues/97) を参照してください。

### サードパーティプラットフォームへのデプロイ
<details>
<summary><strong>Sealos へのデプロイ</strong></summary>
<div>

> Sealos は、高い同時実行性、ダイナミックなスケーリング、数百万人のユーザーに対する安定した運用をサポートしています。

> 下のボタンをクリックすると、ワンクリックで展開できます。👇

[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy?templateName=one-api)


</div>
</details>

<details>
<summary><strong>Zeabur へのデプロイ</strong></summary>
<div>

> Zeabur のサーバーは海外にあるため、ネットワークの問題は自動的に解決されます。

[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/7Q0KO3)

1. まず、コードをフォークする。
2. [Zeabur](https://zeabur.com?referralCode=songquanpeng) にアクセスしてログインし、コンソールに入る。
3. 新しいプロジェクトを作成します。Service -> Add ServiceでMarketplace を選択し、MySQL を選択する。接続パラメータ(ユーザー名、パスワード、アドレス、ポート)をメモします。
4. 接続パラメータをコピーし、```create database `one-api` ``` を実行してデータベースを作成する。
5. その後、Service -> Add Service で Git を選択し(最初の使用には認証が必要です)、フォークしたリポジトリを選択します。
6. 自動デプロイが開始されますが、一旦キャンセルしてください。Variable タブで `PORT` に `3000` を追加し、`SQL_DSN` に `<username>:<password>@tcp(<addr>:<port>)/one-api` を追加します。変更を保存する。SQL_DSN` が設定されていないと、データが永続化されず、再デプロイ後にデータが失われるので注意すること。
7. 再デプロイを選択します。
8. Domains タブで、"my-one-api" のような適切なドメイン名の接頭辞を選択する。最終的なドメイン名は "my-one-api.zeabur.app" となります。独自のドメイン名を CNAME することもできます。
9. デプロイが完了するのを待ち、生成されたドメイン名をクリックして One API にアクセスします。

</div>
</details>

## コンフィグ
システムは箱から出してすぐに使えます。

環境変数やコマンドラインパラメータを設定することで、システムを構成することができます。

システム起動後、`root` ユーザーとしてログインし、さらにシステムを設定します。

## 使用方法
`Channels` ページで API Key を追加し、`Tokens` ページでアクセストークンを追加する。

アクセストークンを使って One API にアクセスすることができる。使い方は [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) と同じです。

OpenAI API が使用されている場所では、API Base に One API のデプロイアドレスを設定することを忘れないでください(例: `https://openai.justsong.cn`)。API Key は One API で生成されたトークンでなければなりません。

具体的な API Base のフォーマットは、使用しているクライアントに依存することに注意してください。

```mermaid
graph LR
    A(ユーザ)
    A --->|リクエスト| B(One API)
    B -->|中継リクエスト| C(OpenAI)
    B -->|中継リクエスト| D(Azure)
    B -->|中継リクエスト| E(その他のダウンストリームチャンネル)
```

現在のリクエストにどのチャネルを使うかを指定するには、トークンの後に チャネル ID を追加します: 例えば、`Authorization: Bearer ONE_API_KEY-CHANNEL_ID` のようにします。
チャンネル ID を指定するためには、トークンは管理者によって作成される必要があることに注意してください。

もしチャネル ID が指定されない場合、ロードバランシングによってリクエストが複数のチャネルに振り分けられます。

### 環境変数
1. `REDIS_CONN_STRING`: 設定すると、リクエストレート制限のためのストレージとして、メモリの代わりに Redis が使われる。
    + 例: `REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
2. `SESSION_SECRET`: 設定すると、固定セッションキーが使用され、システムの再起動後もログインユーザーのクッキーが有効であることが保証されます。
    + 例: `SESSION_SECRET=random_string`
3. `SQL_DSN`: 設定すると、SQLite の代わりに指定したデータベースが使用されます。MySQL バージョン 8.0 を使用してください。
    + 例: `SQL_DSN=root:123456@tcp(localhost:3306)/oneapi`
4. `LOG_SQL_DSN`: を設定すると、`logs`テーブルには独立したデータベースが使用されます。MySQLまたはPostgreSQLを使用してください。
5. `FRONTEND_BASE_URL`: 設定されると、バックエンドアドレスではなく、指定されたフロントエンドアドレスが使われる。
    + 例: `FRONTEND_BASE_URL=https://openai.justsong.cn`
6. `SYNC_FREQUENCY`: 設定された場合、システムは定期的にデータベースからコンフィグを秒単位で同期する。設定されていない場合、同期は行われません。
    + 例: `SYNC_FREQUENCY=60`
7. `NODE_TYPE`: 設定すると、ノードのタイプを指定する。有効な値は `master` と `slave` である。設定されていない場合、デフォルトは `master`。
    + 例: `NODE_TYPE=slave`
8. `CHANNEL_UPDATE_FREQUENCY`: 設定すると、チャンネル残高を分単位で定期的に更新する。設定されていない場合、更新は行われません。
    + 例: `CHANNEL_UPDATE_FREQUENCY=1440`
9. `CHANNEL_TEST_FREQUENCY`: 設定すると、チャンネルを定期的にテストする。設定されていない場合、テストは行われません。
    + 例: `CHANNEL_TEST_FREQUENCY=1440`
10. `POLLING_INTERVAL`: チャネル残高の更新とチャネルの可用性をテストするときのリクエスト間の時間間隔 (秒)。デフォルトは間隔なし。
    + 例: `POLLING_INTERVAL=5`

### コマンドラインパラメータ
1. `--port <port_number>`: サーバがリッスンするポート番号を指定。デフォルトは `3000` です。
    + 例: `--port 3000`
2. `--log-dir <log_dir>`: ログディレクトリを指定。設定しない場合、ログは保存されません。
    + 例: `--log-dir ./logs`
3. `--version`: システムのバージョン番号を表示して終了する。
4. `--help`: コマンドの使用法ヘルプとパラメータの説明を表示。

## スクリーンショット
![channel](https://user-images.githubusercontent.com/39998050/233837954-ae6683aa-5c4f-429f-a949-6645a83c9490.png)
![token](https://user-images.githubusercontent.com/39998050/233837971-dab488b7-6d96-43af-b640-a168e8d1c9bf.png)

## FAQ
1. ノルマとは何か?どのように計算されますか?One API にはノルマ計算の問題はありますか?
    + ノルマ = グループ倍率 * モデル倍率 * (プロンプトトークンの数 + 完了トークンの数 * 完了倍率)
    + 完了倍率は、公式の定義と一致するように、GPT3.5 では 1.33、GPT4 では 2 に固定されています。
    + ストリームモードでない場合、公式 API は消費したトークンの総数を返す。ただし、プロンプトとコンプリートの消費倍率は異なるので注意してください。
2. アカウント残高は十分なのに、"insufficient quota" と表示されるのはなぜですか?
    + トークンのクォータが十分かどうかご確認ください。トークンクォータはアカウント残高とは別のものです。
    + トークンクォータは最大使用量を設定するためのもので、ユーザーが自由に設定できます。
3. チャンネルを使おうとすると "No available channels" と表示されます。どうすればいいですか?
    + ユーザーとチャンネルグループの設定を確認してください。
    + チャンネルモデルの設定も確認してください。
4. チャンネルテストがエラーを報告する: "invalid character '<' looking for beginning of value"
    + このエラーは、返された値が有効な JSON ではなく、HTML ページである場合に発生する。
    + ほとんどの場合、デプロイサイトのIPかプロキシのノードが CloudFlare によってブロックされています。
5. ChatGPT Next Web でエラーが発生しました: "Failed to fetch"
    + デプロイ時に `BASE_URL` を設定しないでください。
    + インターフェイスアドレスと API Key が正しいか再確認してください。

## 関連プロジェクト
* [FastGPT](https://github.com/labring/FastGPT): LLM に基づく知識質問応答システム
* [CherryStudio](https://github.com/CherryHQ/cherry-studio):  マルチプラットフォーム対応のAIクライアント。複数のサービスプロバイダーを統合管理し、ローカル知識ベースをサポートします。
## 注
本プロジェクトはオープンソースプロジェクトです。OpenAI の[利用規約](https://openai.com/policies/terms-of-use)および**適用される法令**を遵守してご利用ください。違法な目的での利用はご遠慮ください。

このプロジェクトは MIT ライセンスで公開されています。これに基づき、ページの最下部に帰属表示と本プロジェクトへのリンクを含める必要があります。

このプロジェクトを基にした派生プロジェクトについても同様です。

帰属表示を含めたくない場合は、事前に許可を得なければなりません。

MIT ライセンスによると、このプロジェクトを利用するリスクと責任は利用者が負うべきであり、このオープンソースプロジェクトの開発者は責任を負いません。


================================================
FILE: README.md
================================================
<p align="right">
   <strong>中文</strong> | <a href="./README.en.md">English</a> | <a href="./README.ja.md">日本語</a>
</p>


<p align="center">
  <a href="https://github.com/songquanpeng/one-api"><img src="https://raw.githubusercontent.com/songquanpeng/one-api/main/web/default/public/logo.png" width="150" height="150" alt="one-api logo"></a>
</p>

<div align="center">

# One API

_✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 ✨_

</div>

<p align="center">
  <a href="https://raw.githubusercontent.com/songquanpeng/one-api/main/LICENSE">
    <img src="https://img.shields.io/github/license/songquanpeng/one-api?color=brightgreen" alt="license">
  </a>
  <a href="https://github.com/songquanpeng/one-api/releases/latest">
    <img src="https://img.shields.io/github/v/release/songquanpeng/one-api?color=brightgreen&include_prereleases" alt="release">
  </a>
  <a href="https://hub.docker.com/repository/docker/justsong/one-api">
    <img src="https://img.shields.io/docker/pulls/justsong/one-api?color=brightgreen" alt="docker pull">
  </a>
  <a href="https://github.com/songquanpeng/one-api/releases/latest">
    <img src="https://img.shields.io/github/downloads/songquanpeng/one-api/total?color=brightgreen&include_prereleases" alt="release">
  </a>
  <a href="https://goreportcard.com/report/github.com/songquanpeng/one-api">
    <img src="https://goreportcard.com/badge/github.com/songquanpeng/one-api" alt="GoReportCard">
  </a>
</p>

<p align="center">
  <a href="https://github.com/songquanpeng/one-api#部署">部署教程</a>
  ·
  <a href="https://github.com/songquanpeng/one-api#使用方法">使用方法</a>
  ·
  <a href="https://github.com/songquanpeng/one-api/issues">意见反馈</a>
  ·
  <a href="https://github.com/songquanpeng/one-api#截图展示">截图展示</a>
  ·
  <a href="https://openai.justsong.cn/">在线演示</a>
  ·
  <a href="https://github.com/songquanpeng/one-api#常见问题">常见问题</a>
  ·
  <a href="https://github.com/songquanpeng/one-api#相关项目">相关项目</a>
  ·
  <a href="https://iamazing.cn/page/reward">赞赏支持</a>
</p>

> [!NOTE]
> 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。
>
> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务。

> [!NOTE]
> 稳定版 / 预览版镜像地址:[justsong/one-api](https://hub.docker.com/repository/docker/justsong/one-api)
> 或者 [ghcr.io/songquanpeng/one-api](https://github.com/songquanpeng/one-api/pkgs/container/one-api)
>
> alpha 版镜像地址:[justsong/one-api-alpha](https://hub.docker.com/repository/docker/justsong/one-api-alpha)
> 或者 [ghcr.io/songquanpeng/one-api-alpha](https://github.com/songquanpeng/one-api/pkgs/container/one-api-alpha)

> [!WARNING]
> 使用 root 用户初次登录系统后,务必修改默认密码 `123456`!

## 功能
1. 支持多种大模型:
   + [x] [OpenAI ChatGPT 系列模型](https://platform.openai.com/docs/guides/gpt/chat-completions-api)(支持 [Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference))
   + [x] [Anthropic Claude 系列模型](https://anthropic.com) (支持 AWS Claude)
   + [x] [Google PaLM2/Gemini 系列模型](https://developers.generativeai.google)
   + [x] [Mistral 系列模型](https://mistral.ai/)
   + [x] [字节跳动豆包大模型(火山引擎)](https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=2QXCA1VI)
   + [x] [百度文心一言系列模型](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html)
   + [x] [阿里通义千问系列模型](https://help.aliyun.com/document_detail/2400395.html)
   + [x] [讯飞星火认知大模型](https://www.xfyun.cn/doc/spark/Web.html)
   + [x] [智谱 ChatGLM 系列模型](https://bigmodel.cn)
   + [x] [360 智脑](https://ai.360.cn)
   + [x] [腾讯混元大模型](https://cloud.tencent.com/document/product/1729)
   + [x] [Moonshot AI](https://platform.moonshot.cn/)
   + [x] [百川大模型](https://platform.baichuan-ai.com)
   + [x] [MINIMAX](https://api.minimax.chat/)
   + [x] [Groq](https://wow.groq.com/)
   + [x] [Ollama](https://github.com/ollama/ollama)
   + [x] [零一万物](https://platform.lingyiwanwu.com/)
   + [x] [阶跃星辰](https://platform.stepfun.com/)
   + [x] [Coze](https://www.coze.com/)
   + [x] [Cohere](https://cohere.com/)
   + [x] [DeepSeek](https://www.deepseek.com/)
   + [x] [Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai/)
   + [x] [DeepL](https://www.deepl.com/)
   + [x] [together.ai](https://www.together.ai/)
   + [x] [novita.ai](https://www.novita.ai/)
   + [x] [硅基流动 SiliconCloud](https://cloud.siliconflow.cn/i/rKXmRobW)
   + [x] [xAI](https://x.ai/)
2. 支持配置镜像以及众多[第三方代理服务](https://iamazing.cn/page/openai-api-third-party-services)。
3. 支持通过**负载均衡**的方式访问多个渠道。
4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。
5. 支持**多机部署**,[详见此处](#多机部署)。
6. 支持**令牌管理**,设置令牌的过期时间、额度、允许的 IP 范围以及允许的模型访问。
7. 支持**兑换码管理**,支持批量生成和导出兑换码,可使用兑换码为账户进行充值。
8. 支持**渠道管理**,批量创建渠道。
9. 支持**用户分组**以及**渠道分组**,支持为不同分组设置不同的倍率。
10. 支持渠道**设置模型列表**。
11. 支持**查看额度明细**。
12. 支持**用户邀请奖励**。
13. 支持以美元为单位显示额度。
14. 支持发布公告,设置充值链接,设置新用户初始额度。
15. 支持模型映射,重定向用户的请求模型,如无必要请不要设置,设置之后会导致请求体被重新构造而非直接透传,会导致部分还未正式支持的字段无法传递成功。
16. 支持失败自动重试。
17. 支持绘图接口。
18. 支持 [Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/providers/openai/),渠道设置的代理部分填写 `https://gateway.ai.cloudflare.com/v1/ACCOUNT_TAG/GATEWAY/openai` 即可。
19. 支持丰富的**自定义**设置,
    1. 支持自定义系统名称,logo 以及页脚。
    2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
20. 支持通过系统访问令牌调用管理 API,进而**在无需二开的情况下扩展和自定义** One API 的功能,详情请参考此处 [API 文档](./docs/API.md)。
21. 支持 Cloudflare Turnstile 用户校验。
22. 支持用户管理,支持**多种用户登录注册方式**:
    + 邮箱登录注册(支持注册邮箱白名单)以及通过邮箱进行密码重置。
    + 支持[飞书授权登录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/authorize/get)([这里有 One API 的实现细节阐述供参考](https://iamazing.cn/page/feishu-oauth-login))。
    + 支持 [GitHub 授权登录](https://github.com/settings/applications/new)。
    + 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
23. 支持主题切换,设置环境变量 `THEME` 即可,默认为 `default`,欢迎 PR 更多主题,具体参考[此处](./web/README.md)。
24. 配合 [Message Pusher](https://github.com/songquanpeng/message-pusher) 可将报警信息推送到多种 App 上。

## 部署
### 基于 Docker 进行部署
```shell
# 使用 SQLite 的部署命令:
docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api
# 使用 MySQL 的部署命令,在上面的基础上添加 `-e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi"`,请自行修改数据库连接参数,不清楚如何修改请参见下面环境变量一节。
# 例如:
docker run --name one-api -d --restart always -p 3000:3000 -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api
```

其中,`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。

数据和日志将会保存在宿主机的 `/home/ubuntu/data/one-api` 目录,请确保该目录存在且具有写入权限,或者更改为合适的目录。

如果启动失败,请添加 `--privileged=true`,具体参考 https://github.com/songquanpeng/one-api/issues/482 。

如果上面的镜像无法拉取,可以尝试使用 GitHub 的 Docker 镜像,将上面的 `justsong/one-api` 替换为 `ghcr.io/songquanpeng/one-api` 即可。

如果你的并发量较大,**务必**设置 `SQL_DSN`,详见下面[环境变量](#环境变量)一节。

更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR`

Nginx 的参考配置:
```
server{
   server_name openai.justsong.cn;  # 请根据实际情况修改你的域名

   location / {
          client_max_body_size  64m;
          proxy_http_version 1.1;
          proxy_pass http://localhost:3000;  # 请根据实际情况修改你的端口
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $remote_addr;
          proxy_cache_bypass $http_upgrade;
          proxy_set_header Accept-Encoding gzip;
          proxy_read_timeout 300s;  # GPT-4 需要较长的超时时间,请自行调整
   }
}
```

之后使用 Let's Encrypt 的 certbot 配置 HTTPS:
```bash
# Ubuntu 安装 certbot:
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# 生成证书 & 修改 Nginx 配置
sudo certbot --nginx
# 根据指示进行操作
# 重启 Nginx
sudo service nginx restart
```

初始账号用户名为 `root`,密码为 `123456`。

### 通过宝塔面板进行一键部署
1. 安装宝塔面板9.2.0及以上版本,前往 [宝塔面板](https://www.bt.cn/new/download.html?r=dk_oneapi) 官网,选择正式版的脚本下载安装;
2. 安装后登录宝塔面板,在左侧菜单栏中点击 `Docker`,首次进入会提示安装 `Docker` 服务,点击立即安装,按提示完成安装;
3. 安装完成后在应用商店中搜索 `One-API`,点击安装,配置域名等基本信息即可完成安装;

### 基于 Docker Compose 进行部署

> 仅启动方式不同,参数设置不变,请参考基于 Docker 部署部分

```shell
# 目前支持 MySQL 启动,数据存储在 ./data/mysql 文件夹内
docker-compose up -d

# 查看部署状态
docker-compose ps
```

### 手动部署
1. 从 [GitHub Releases](https://github.com/songquanpeng/one-api/releases/latest) 下载可执行文件或者从源码编译:
   ```shell
   git clone https://github.com/songquanpeng/one-api.git

   # 构建前端
   cd one-api/web/default
   npm install
   npm run build

   # 构建后端
   cd ../..
   go mod download
   go build -ldflags "-s -w" -o one-api
   ````
2. 运行:
   ```shell
   chmod u+x one-api
   ./one-api --port 3000 --log-dir ./logs
   ```
3. 访问 [http://localhost:3000/](http://localhost:3000/) 并登录。初始账号用户名为 `root`,密码为 `123456`。

更加详细的部署教程[参见此处](https://iamazing.cn/page/how-to-deploy-a-website)。

### 多机部署
1. 所有服务器 `SESSION_SECRET` 设置一样的值。
2. 必须设置 `SQL_DSN`,使用 MySQL 数据库而非 SQLite,所有服务器连接同一个数据库。
3. 所有从服务器必须设置 `NODE_TYPE` 为 `slave`,不设置则默认为主服务器。
4. 设置 `SYNC_FREQUENCY` 后服务器将定期从数据库同步配置,在使用远程数据库的情况下,推荐设置该项并启用 Redis,无论主从。
5. 从服务器可以选择设置 `FRONTEND_BASE_URL`,以重定向页面请求到主服务器。
6. 从服务器上**分别**装好 Redis,设置好 `REDIS_CONN_STRING`,这样可以做到在缓存未过期的情况下数据库零访问,可以减少延迟(Redis 集群或者哨兵模式的支持请参考环境变量说明)。
7. 如果主服务器访问数据库延迟也比较高,则也需要启用 Redis,并设置 `SYNC_FREQUENCY`,以定期从数据库同步配置。

环境变量的具体使用方法详见[此处](#环境变量)。

### 宝塔部署教程

详见 [#175](https://github.com/songquanpeng/one-api/issues/175)。

如果部署后访问出现空白页面,详见 [#97](https://github.com/songquanpeng/one-api/issues/97)。

### 部署第三方服务配合 One API 使用
> 欢迎 PR 添加更多示例。

#### ChatGPT Next Web
项目主页:https://github.com/Yidadaa/ChatGPT-Next-Web

```bash
docker run --name chat-next-web -d -p 3001:3000 yidadaa/chatgpt-next-web
```

注意修改端口号,之后在页面上设置接口地址(例如:https://openai.justsong.cn/ )和 API Key 即可。

#### ChatGPT Web
项目主页:https://github.com/Chanzhaoyu/chatgpt-web

```bash
docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://openai.justsong.cn -e OPENAI_API_KEY=sk-xxx chenzhaoyu94/chatgpt-web
```

注意修改端口号、`OPENAI_API_BASE_URL` 和 `OPENAI_API_KEY`。

#### QChatGPT - QQ机器人
项目主页:https://github.com/RockChinQ/QChatGPT

根据[文档](https://qchatgpt.rockchin.top)完成部署后,在 `data/provider.json`设置`requester.openai-chat-completions.base-url`为 One API 实例地址,并填写 API Key 到 `keys.openai` 组中,设置 `model` 为要使用的模型名称。

运行期间可以通过`!model`命令查看、切换可用模型。

### 部署到第三方平台
<details>
<summary><strong>部署到 Sealos </strong></summary>
<div>

> Sealos 的服务器在国外,不需要额外处理网络问题,支持高并发 & 动态伸缩。

点击以下按钮一键部署(部署后访问出现 404 请等待 3~5 分钟):

[![Deploy-on-Sealos.svg](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy?templateName=one-api)

</div>
</details>

<details>
<summary><strong>部署到 Zeabur</strong></summary>
<div>

> Zeabur 的服务器在国外,自动解决了网络的问题,同时免费的额度也足够个人使用

[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/7Q0KO3)

1. 首先 fork 一份代码。
2. 进入 [Zeabur](https://zeabur.com?referralCode=songquanpeng),登录,进入控制台。
3. 新建一个 Project,在 Service -> Add Service 选择 Marketplace,选择 MySQL,并记下连接参数(用户名、密码、地址、端口)。
4. 复制链接参数,运行 ```create database `one-api` ``` 创建数据库。
5. 然后在 Service -> Add Service,选择 Git(第一次使用需要先授权),选择你 fork 的仓库。
6. Deploy 会自动开始,先取消。进入下方 Variable,添加一个 `PORT`,值为 `3000`,再添加一个 `SQL_DSN`,值为 `<username>:<password>@tcp(<addr>:<port>)/one-api` ,然后保存。 注意如果不填写 `SQL_DSN`,数据将无法持久化,重新部署后数据会丢失。
7. 选择 Redeploy。
8. 进入下方 Domains,选择一个合适的域名前缀,如 "my-one-api",最终域名为 "my-one-api.zeabur.app",也可以 CNAME 自己的域名。
9. 等待部署完成,点击生成的域名进入 One API。

</div>
</details>

<details>
<summary><strong>部署到 Render</strong></summary>
<div>

> Render 提供免费额度,绑卡后可以进一步提升额度

Render 可以直接部署 docker 镜像,不需要 fork 仓库:https://dashboard.render.com

</div>
</details>

## 配置
系统本身开箱即用。

你可以通过设置环境变量或者命令行参数进行配置。

等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。

**Note**:如果你不知道某个配置项的含义,可以临时删掉值以看到进一步的提示文字。

## 使用方法
在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。

之后就可以使用你的令牌访问 One API 了,使用方式与 [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) 一致。

你需要在各种用到 OpenAI API 的地方设置 API Base 为你的 One API 的部署地址,例如:`https://openai.justsong.cn`,API Key 则为你在 One API 中生成的令牌。

注意,具体的 API Base 的格式取决于你所使用的客户端。

例如对于 OpenAI 的官方库:
```bash
OPENAI_API_KEY="sk-xxxxxx"
OPENAI_API_BASE="https://<HOST>:<PORT>/v1"
```

```mermaid
graph LR
    A(用户)
    A --->|使用 One API 分发的 key 进行请求| B(One API)
    B -->|中继请求| C(OpenAI)
    B -->|中继请求| D(Azure)
    B -->|中继请求| E(其他 OpenAI API 格式下游渠道)
    B -->|中继并修改请求体和返回体| F(非 OpenAI API 格式下游渠道)
```

可以通过在令牌后面添加渠道 ID 的方式指定使用哪一个渠道处理本次请求,例如:`Authorization: Bearer ONE_API_KEY-CHANNEL_ID`。
注意,需要是管理员用户创建的令牌才能指定渠道 ID。

不加的话将会使用负载均衡的方式使用多个渠道。

### 环境变量
> One API 支持从 `.env` 文件中读取环境变量,请参照 `.env.example` 文件,使用时请将其重命名为 `.env`。
1. `REDIS_CONN_STRING`:设置之后将使用 Redis 作为缓存使用。
   + 例子:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
   + 如果数据库访问延迟很低,没有必要启用 Redis,启用后反而会出现数据滞后的问题。
   + 如果需要使用哨兵或者集群模式:
     + 则需要把该环境变量设置为节点列表,例如:`localhost:49153,localhost:49154,localhost:49155`。
     + 除此之外还需要设置以下环境变量:
       + `REDIS_PASSWORD`:Redis 集群或者哨兵模式下的密码设置。
       + `REDIS_MASTER_NAME`:Redis 哨兵模式下主节点的名称。
2. `SESSION_SECRET`:设置之后将使用固定的会话密钥,这样系统重新启动后已登录用户的 cookie 将依旧有效。
   + 例子:`SESSION_SECRET=random_string`
3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 或 PostgreSQL。
   + 例子:
     + MySQL:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi`
     + PostgreSQL:`SQL_DSN=postgres://postgres:123456@localhost:5432/oneapi`(适配中,欢迎反馈)
   + 注意需要提前建立数据库 `oneapi`,无需手动建表,程序将自动建表。
   + 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。
   + 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。
   + 请根据你的数据库配置修改下列参数(或者保持默认值):
     + `SQL_MAX_IDLE_CONNS`:最大空闲连接数,默认为 `100`。
     + `SQL_MAX_OPEN_CONNS`:最大打开连接数,默认为 `1000`。
       + 如果报错 `Error 1040: Too many connections`,请适当减小该值。
     + `SQL_CONN_MAX_LIFETIME`:连接的最大生命周期,默认为 `60`,单位分钟。
4. `LOG_SQL_DSN`:设置之后将为 `logs` 表使用独立的数据库,请使用 MySQL 或 PostgreSQL。
5. `FRONTEND_BASE_URL`:设置之后将重定向页面请求到指定的地址,仅限从服务器设置。
   + 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn`
6. `MEMORY_CACHE_ENABLED`:启用内存缓存,会导致用户额度的更新存在一定的延迟,可选值为 `true` 和 `false`,未设置则默认为 `false`。
   + 例子:`MEMORY_CACHE_ENABLED=true`
7. `SYNC_FREQUENCY`:在启用缓存的情况下与数据库同步配置的频率,单位为秒,默认为 `600` 秒。
   + 例子:`SYNC_FREQUENCY=60`
8. `NODE_TYPE`:设置之后将指定节点类型,可选值为 `master` 和 `slave`,未设置则默认为 `master`。
   + 例子:`NODE_TYPE=slave`
9. `CHANNEL_UPDATE_FREQUENCY`:设置之后将定期更新渠道余额,单位为分钟,未设置则不进行更新。
   + 例子:`CHANNEL_UPDATE_FREQUENCY=1440`
10. `CHANNEL_TEST_FREQUENCY`:设置之后将定期检查渠道,单位为分钟,未设置则不进行检查。 
   +例子:`CHANNEL_TEST_FREQUENCY=1440`
11. `POLLING_INTERVAL`:批量更新渠道余额以及测试可用性时的请求间隔,单位为秒,默认无间隔。
    + 例子:`POLLING_INTERVAL=5`
12. `BATCH_UPDATE_ENABLED`:启用数据库批量更新聚合,会导致用户额度的更新存在一定的延迟可选值为 `true` 和 `false`,未设置则默认为 `false`。
    + 例子:`BATCH_UPDATE_ENABLED=true`
    + 如果你遇到了数据库连接数过多的问题,可以尝试启用该选项。
13. `BATCH_UPDATE_INTERVAL=5`:批量更新聚合的时间间隔,单位为秒,默认为 `5`。
    + 例子:`BATCH_UPDATE_INTERVAL=5`
14. 请求频率限制:
    + `GLOBAL_API_RATE_LIMIT`:全局 API 速率限制(除中继请求外),单 ip 三分钟内的最大请求数,默认为 `180`。
    + `GLOBAL_WEB_RATE_LIMIT`:全局 Web 速率限制,单 ip 三分钟内的最大请求数,默认为 `60`。
15. 编码器缓存设置:
    + `TIKTOKEN_CACHE_DIR`:默认程序启动时会联网下载一些通用的词元的编码,如:`gpt-3.5-turbo`,在一些网络环境不稳定,或者离线情况,可能会导致启动有问题,可以配置此目录缓存数据,可迁移到离线环境。
    + `DATA_GYM_CACHE_DIR`:目前该配置作用与 `TIKTOKEN_CACHE_DIR` 一致,但是优先级没有它高。
16. `RELAY_TIMEOUT`:中继超时设置,单位为秒,默认不设置超时时间。
17. `RELAY_PROXY`:设置后使用该代理来请求 API。
18. `USER_CONTENT_REQUEST_TIMEOUT`:用户上传内容下载超时时间,单位为秒。
19. `USER_CONTENT_REQUEST_PROXY`:设置后使用该代理来请求用户上传的内容,例如图片。
20. `SQLITE_BUSY_TIMEOUT`:SQLite 锁等待超时设置,单位为毫秒,默认 `3000`。
21. `GEMINI_SAFETY_SETTING`:Gemini 的安全设置,默认 `BLOCK_NONE`。
22. `GEMINI_VERSION`:One API 所使用的 Gemini 版本,默认为 `v1`。
23. `THEME`:系统的主题设置,默认为 `default`,具体可选值参考[此处](./web/README.md)。
24. `ENABLE_METRIC`:是否根据请求成功率禁用渠道,默认不开启,可选值为 `true` 和 `false`。
25. `METRIC_QUEUE_SIZE`:请求成功率统计队列大小,默认为 `10`。
26. `METRIC_SUCCESS_RATE_THRESHOLD`:请求成功率阈值,默认为 `0.8`。
27. `INITIAL_ROOT_TOKEN`:如果设置了该值,则在系统首次启动时会自动创建一个值为该环境变量值的 root 用户令牌。
28. `INITIAL_ROOT_ACCESS_TOKEN`:如果设置了该值,则在系统首次启动时会自动创建一个值为该环境变量的 root 用户创建系统管理令牌。
29. `ENFORCE_INCLUDE_USAGE`:是否强制在 stream 模型下返回 usage,默认不开启,可选值为 `true` 和 `false`。
30. `TEST_PROMPT`:测试模型时的用户 prompt,默认为 `Print your model name exactly and do not output without any other text.`。

### 命令行参数
1. `--port <port_number>`: 指定服务器监听的端口号,默认为 `3000`。
   + 例子:`--port 3000`
2. `--log-dir <log_dir>`: 指定日志文件夹,如果没有设置,默认保存至工作目录的 `logs` 文件夹下。
   + 例子:`--log-dir ./logs`
3. `--version`: 打印系统版本号并退出。
4. `--help`: 查看命令的使用帮助和参数说明。

## 演示
### 在线演示
注意,该演示站不提供对外服务:
https://openai.justsong.cn

### 截图展示
![channel](https://user-images.githubusercontent.com/39998050/233837954-ae6683aa-5c4f-429f-a949-6645a83c9490.png)
![token](https://user-images.githubusercontent.com/39998050/233837971-dab488b7-6d96-43af-b640-a168e8d1c9bf.png)

## 常见问题
1. 额度是什么?怎么计算的?One API 的额度计算有问题?
   + 额度 = 分组倍率 * 模型倍率 * (提示 token 数 + 补全 token 数 * 补全倍率)
   + 其中补全倍率对于 GPT3.5 固定为 1.33,GPT4 为 2,与官方保持一致。
   + 如果是非流模式,官方接口会返回消耗的总 token,但是你要注意提示和补全的消耗倍率不一样。
   + 注意,One API 的默认倍率就是官方倍率,是已经调整过的。
2. 账户额度足够为什么提示额度不足?
   + 请检查你的令牌额度是否足够,这个和账户额度是分开的。
   + 令牌额度仅供用户设置最大使用量,用户可自由设置。
3. 提示无可用渠道?
   + 请检查的用户分组和渠道分组设置。
   + 以及渠道的模型设置。
4. 渠道测试报错:`invalid character '<' looking for beginning of value`
   + 这是因为返回值不是合法的 JSON,而是一个 HTML 页面。
   + 大概率是你的部署站的 IP 或代理的节点被 CloudFlare 封禁了。
5. ChatGPT Next Web 报错:`Failed to fetch`
   + 部署的时候不要设置 `BASE_URL`。
   + 检查你的接口地址和 API Key 有没有填对。
   + 检查是否启用了 HTTPS,浏览器会拦截 HTTPS 域名下的 HTTP 请求。
6. 报错:`当前分组负载已饱和,请稍后再试`
   + 上游渠道 429 了。
7. 升级之后我的数据会丢失吗?
   + 如果使用 MySQL,不会。
   + 如果使用 SQLite,需要按照我所给的部署命令挂载 volume 持久化 one-api.db 数据库文件,否则容器重启后数据会丢失。
8. 升级之前数据库需要做变更吗?
   + 一般情况下不需要,系统将在初始化的时候自动调整。
   + 如果需要的话,我会在更新日志中说明,并给出脚本。
9. 手动修改数据库后报错:`数据库一致性已被破坏,请联系管理员`?
   + 这是检测到 ability 表里有些记录的渠道 id 是不存在的,这大概率是因为你删了 channel 表里的记录但是没有同步在 ability 表里清理无效的渠道。
   + 对于每一个渠道,其所支持的模型都需要有一个专门的 ability 表的记录,表示该渠道支持该模型。

## 相关项目
* [FastGPT](https://github.com/labring/FastGPT): 基于 LLM 大语言模型的知识库问答系统
* [ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web):  一键拥有你自己的跨平台 ChatGPT 应用
* [VChart](https://github.com/VisActor/VChart):  不只是开箱即用的多端图表库,更是生动灵活的数据故事讲述者。
* [VMind](https://github.com/VisActor/VMind):  不仅自动,还很智能。开源智能可视化解决方案。
* [CherryStudio](https://github.com/CherryHQ/cherry-studio):  全平台支持的AI客户端, 多服务商集成管理、本地知识库支持。

## 注意

本项目使用 MIT 协议进行开源,**在此基础上**,必须在页面底部保留署名以及指向本项目的链接。如果不想保留署名,必须首先获得授权。

同样适用于基于本项目的二开项目。

依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。


================================================
FILE: VERSION
================================================


================================================
FILE: bin/migration_v0.2-v0.3.sql
================================================
UPDATE users
SET quota = quota + (
    SELECT SUM(remain_quota)
    FROM tokens
    WHERE tokens.user_id = users.id
)


================================================
FILE: bin/migration_v0.3-v0.4.sql
================================================
INSERT INTO abilities (`group`, model, channel_id, enabled)
SELECT c.`group`, m.model, c.id, 1
FROM channels c
CROSS JOIN (
    SELECT 'gpt-3.5-turbo' AS model UNION ALL
    SELECT 'gpt-3.5-turbo-0301' AS model UNION ALL
    SELECT 'gpt-4' AS model UNION ALL
    SELECT 'gpt-4-0314' AS model
) AS m
WHERE c.status = 1
  AND NOT EXISTS (
    SELECT 1
    FROM abilities a
    WHERE a.`group` = c.`group`
      AND a.model = m.model
      AND a.channel_id = c.id
);


================================================
FILE: bin/time_test.sh
================================================
#!/bin/bash

if [ $# -lt 3 ]; then
  echo "Usage: time_test.sh <domain> <key> <count> [<model>]"
  exit 1
fi

domain=$1
key=$2
count=$3
model=${4:-"gpt-3.5-turbo"} # 设置默认模型为 gpt-3.5-turbo

total_time=0
times=()

for ((i=1; i<=count; i++)); do
  result=$(curl -o /dev/null -s -w "%{http_code} %{time_total}\\n" \
           https://"$domain"/v1/chat/completions \
           -H "Content-Type: application/json" \
           -H "Authorization: Bearer $key" \
           -d '{"messages": [{"content": "echo hi", "role": "user"}], "model": "'"$model"'", "stream": false, "max_tokens": 1}')
  http_code=$(echo "$result" | awk '{print $1}')
  time=$(echo "$result" | awk '{print $2}')
  echo "HTTP status code: $http_code, Time taken: $time"
  total_time=$(bc <<< "$total_time + $time")
  times+=("$time")
done

average_time=$(echo "scale=4; $total_time / $count" | bc)

sum_of_squares=0
for time in "${times[@]}"; do
  difference=$(echo "scale=4; $time - $average_time" | bc)
  square=$(echo "scale=4; $difference * $difference" | bc)
  sum_of_squares=$(echo "scale=4; $sum_of_squares + $square" | bc)
done

standard_deviation=$(echo "scale=4; sqrt($sum_of_squares / $count)" | bc)

echo "Average time: $average_time±$standard_deviation"


================================================
FILE: common/blacklist/main.go
================================================
package blacklist

import (
	"fmt"
	"sync"
)

var blackList sync.Map

func init() {
	blackList = sync.Map{}
}

func userId2Key(id int) string {
	return fmt.Sprintf("userid_%d", id)
}

func BanUser(id int) {
	blackList.Store(userId2Key(id), true)
}

func UnbanUser(id int) {
	blackList.Delete(userId2Key(id))
}

func IsUserBanned(id int) bool {
	_, ok := blackList.Load(userId2Key(id))
	return ok
}


================================================
FILE: common/client/init.go
================================================
package client

import (
	"fmt"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/logger"
	"net/http"
	"net/url"
	"time"
)

var HTTPClient *http.Client
var ImpatientHTTPClient *http.Client
var UserContentRequestHTTPClient *http.Client

func Init() {
	if config.UserContentRequestProxy != "" {
		logger.SysLog(fmt.Sprintf("using %s as proxy to fetch user content", config.UserContentRequestProxy))
		proxyURL, err := url.Parse(config.UserContentRequestProxy)
		if err != nil {
			logger.FatalLog(fmt.Sprintf("USER_CONTENT_REQUEST_PROXY set but invalid: %s", config.UserContentRequestProxy))
		}
		transport := &http.Transport{
			Proxy: http.ProxyURL(proxyURL),
		}
		UserContentRequestHTTPClient = &http.Client{
			Transport: transport,
			Timeout:   time.Second * time.Duration(config.UserContentRequestTimeout),
		}
	} else {
		UserContentRequestHTTPClient = &http.Client{}
	}
	var transport http.RoundTripper
	if config.RelayProxy != "" {
		logger.SysLog(fmt.Sprintf("using %s as api relay proxy", config.RelayProxy))
		proxyURL, err := url.Parse(config.RelayProxy)
		if err != nil {
			logger.FatalLog(fmt.Sprintf("USER_CONTENT_REQUEST_PROXY set but invalid: %s", config.UserContentRequestProxy))
		}
		transport = &http.Transport{
			Proxy: http.ProxyURL(proxyURL),
		}
	}

	if config.RelayTimeout == 0 {
		HTTPClient = &http.Client{
			Transport: transport,
		}
	} else {
		HTTPClient = &http.Client{
			Timeout:   time.Duration(config.RelayTimeout) * time.Second,
			Transport: transport,
		}
	}

	ImpatientHTTPClient = &http.Client{
		Timeout:   5 * time.Second,
		Transport: transport,
	}
}


================================================
FILE: common/config/config.go
================================================
package config

import (
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/songquanpeng/one-api/common/env"

	"github.com/google/uuid"
)

var SystemName = "One API"
var ServerAddress = "http://localhost:3000"
var Footer = ""
var Logo = ""
var TopUpLink = ""
var ChatLink = ""
var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
var DisplayInCurrencyEnabled = true
var DisplayTokenStatEnabled = true

// Any options with "Secret", "Token" in its key won't be return by GetOptions

var SessionSecret = uuid.New().String()

var OptionMap map[string]string
var OptionMapRWMutex sync.RWMutex

var ItemsPerPage = 10
var MaxRecentItems = 100

var PasswordLoginEnabled = true
var PasswordRegisterEnabled = true
var EmailVerificationEnabled = false
var GitHubOAuthEnabled = false
var OidcEnabled = false
var WeChatAuthEnabled = false
var TurnstileCheckEnabled = false
var RegisterEnabled = true

var EmailDomainRestrictionEnabled = false
var EmailDomainWhitelist = []string{
	"gmail.com",
	"163.com",
	"126.com",
	"qq.com",
	"outlook.com",
	"hotmail.com",
	"icloud.com",
	"yahoo.com",
	"foxmail.com",
}

var DebugEnabled = strings.ToLower(os.Getenv("DEBUG")) == "true"
var DebugSQLEnabled = strings.ToLower(os.Getenv("DEBUG_SQL")) == "true"
var MemoryCacheEnabled = strings.ToLower(os.Getenv("MEMORY_CACHE_ENABLED")) == "true"

var LogConsumeEnabled = true

var SMTPServer = ""
var SMTPPort = 587
var SMTPAccount = ""
var SMTPFrom = ""
var SMTPToken = ""

var GitHubClientId = ""
var GitHubClientSecret = ""

var LarkClientId = ""
var LarkClientSecret = ""

var OidcClientId = ""
var OidcClientSecret = ""
var OidcWellKnown = ""
var OidcAuthorizationEndpoint = ""
var OidcTokenEndpoint = ""
var OidcUserinfoEndpoint = ""

var WeChatServerAddress = ""
var WeChatServerToken = ""
var WeChatAccountQRCodeImageURL = ""

var MessagePusherAddress = ""
var MessagePusherToken = ""

var TurnstileSiteKey = ""
var TurnstileSecretKey = ""

var QuotaForNewUser int64 = 0
var QuotaForInviter int64 = 0
var QuotaForInvitee int64 = 0
var ChannelDisableThreshold = 5.0
var AutomaticDisableChannelEnabled = false
var AutomaticEnableChannelEnabled = false
var QuotaRemindThreshold int64 = 1000
var PreConsumedQuota int64 = 500
var ApproximateTokenEnabled = false
var RetryTimes = 0

var RootUserEmail = ""

var IsMasterNode = os.Getenv("NODE_TYPE") != "slave"

var requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
var RequestInterval = time.Duration(requestInterval) * time.Second

var SyncFrequency = env.Int("SYNC_FREQUENCY", 10*60) // unit is second

var BatchUpdateEnabled = false
var BatchUpdateInterval = env.Int("BATCH_UPDATE_INTERVAL", 5)

var RelayTimeout = env.Int("RELAY_TIMEOUT", 0) // unit is second

var GeminiSafetySetting = env.String("GEMINI_SAFETY_SETTING", "BLOCK_NONE")

var Theme = env.String("THEME", "default")
var ValidThemes = map[string]bool{
	"default": true,
	"berry":   true,
	"air":     true,
}

// All duration's unit is seconds
// Shouldn't larger then RateLimitKeyExpirationDuration
var (
	GlobalApiRateLimitNum            = env.Int("GLOBAL_API_RATE_LIMIT", 480)
	GlobalApiRateLimitDuration int64 = 3 * 60

	GlobalWebRateLimitNum            = env.Int("GLOBAL_WEB_RATE_LIMIT", 240)
	GlobalWebRateLimitDuration int64 = 3 * 60

	UploadRateLimitNum            = 10
	UploadRateLimitDuration int64 = 60

	DownloadRateLimitNum            = 10
	DownloadRateLimitDuration int64 = 60

	CriticalRateLimitNum            = 20
	CriticalRateLimitDuration int64 = 20 * 60
)

var RateLimitKeyExpirationDuration = 20 * time.Minute

var EnableMetric = env.Bool("ENABLE_METRIC", false)
var MetricQueueSize = env.Int("METRIC_QUEUE_SIZE", 10)
var MetricSuccessRateThreshold = env.Float64("METRIC_SUCCESS_RATE_THRESHOLD", 0.8)
var MetricSuccessChanSize = env.Int("METRIC_SUCCESS_CHAN_SIZE", 1024)
var MetricFailChanSize = env.Int("METRIC_FAIL_CHAN_SIZE", 128)

var InitialRootToken = os.Getenv("INITIAL_ROOT_TOKEN")

var InitialRootAccessToken = os.Getenv("INITIAL_ROOT_ACCESS_TOKEN")

var GeminiVersion = env.String("GEMINI_VERSION", "v1")

var OnlyOneLogFile = env.Bool("ONLY_ONE_LOG_FILE", false)

var RelayProxy = env.String("RELAY_PROXY", "")
var UserContentRequestProxy = env.String("USER_CONTENT_REQUEST_PROXY", "")
var UserContentRequestTimeout = env.Int("USER_CONTENT_REQUEST_TIMEOUT", 30)

var EnforceIncludeUsage = env.Bool("ENFORCE_INCLUDE_USAGE", false)
var TestPrompt = env.String("TEST_PROMPT", "Output only your specific model name with no additional text.")


================================================
FILE: common/constants.go
================================================
package common

import "time"

var StartTime = time.Now().Unix() // unit: second
var Version = "v0.0.0"            // this hard coding will be replaced automatically when building, no need to manually change


================================================
FILE: common/conv/any.go
================================================
package conv

func AsString(v any) string {
	str, _ := v.(string)
	return str
}


================================================
FILE: common/crypto.go
================================================
package common

import "golang.org/x/crypto/bcrypt"

func Password2Hash(password string) (string, error) {
	passwordBytes := []byte(password)
	hashedPassword, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)
	return string(hashedPassword), err
}

func ValidatePasswordAndHash(password string, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}


================================================
FILE: common/ctxkey/key.go
================================================
package ctxkey

const (
	Config            = "config"
	Id                = "id"
	Username          = "username"
	Role              = "role"
	Status            = "status"
	Channel           = "channel"
	ChannelId         = "channel_id"
	SpecificChannelId = "specific_channel_id"
	RequestModel      = "request_model"
	ConvertedRequest  = "converted_request"
	OriginalModel     = "original_model"
	Group             = "group"
	ModelMapping      = "model_mapping"
	ChannelName       = "channel_name"
	TokenId           = "token_id"
	TokenName         = "token_name"
	BaseURL           = "base_url"
	AvailableModels   = "available_models"
	KeyRequestBody    = "key_request_body"
	SystemPrompt      = "system_prompt"
)


================================================
FILE: common/custom-event.go
================================================
// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package common

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

type stringWriter interface {
	io.Writer
	writeString(string) (int, error)
}

type stringWrapper struct {
	io.Writer
}

func (w stringWrapper) writeString(str string) (int, error) {
	return w.Writer.Write([]byte(str))
}

func checkWriter(writer io.Writer) stringWriter {
	if w, ok := writer.(stringWriter); ok {
		return w
	} else {
		return stringWrapper{writer}
	}
}

// Server-Sent Events
// W3C Working Draft 29 October 2009
// http://www.w3.org/TR/2009/WD-eventsource-20091029/

var contentType = []string{"text/event-stream"}
var noCache = []string{"no-cache"}

var fieldReplacer = strings.NewReplacer(
	"\n", "\\n",
	"\r", "\\r")

var dataReplacer = strings.NewReplacer(
	"\n", "\ndata:",
	"\r", "\\r")

type CustomEvent struct {
	Event string
	Id    string
	Retry uint
	Data  interface{}
}

func encode(writer io.Writer, event CustomEvent) error {
	w := checkWriter(writer)
	return writeData(w, event.Data)
}

func writeData(w stringWriter, data interface{}) error {
	dataReplacer.WriteString(w, fmt.Sprint(data))
	if strings.HasPrefix(data.(string), "data") {
		w.writeString("\n\n")
	}
	return nil
}

func (r CustomEvent) Render(w http.ResponseWriter) error {
	r.WriteContentType(w)
	return encode(w, r)
}

func (r CustomEvent) WriteContentType(w http.ResponseWriter) {
	header := w.Header()
	header["Content-Type"] = contentType

	if _, exist := header["Cache-Control"]; !exist {
		header["Cache-Control"] = noCache
	}
}


================================================
FILE: common/database.go
================================================
package common

import (
	"github.com/songquanpeng/one-api/common/env"
)

var UsingSQLite = false
var UsingPostgreSQL = false
var UsingMySQL = false

var SQLitePath = "one-api.db"
var SQLiteBusyTimeout = env.Int("SQLITE_BUSY_TIMEOUT", 3000)


================================================
FILE: common/embed-file-system.go
================================================
package common

import (
	"embed"
	"github.com/gin-contrib/static"
	"io/fs"
	"net/http"
)

// Credit: https://github.com/gin-contrib/static/issues/19

type embedFileSystem struct {
	http.FileSystem
}

func (e embedFileSystem) Exists(prefix string, path string) bool {
	_, err := e.Open(path)
	return err == nil
}

func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
	efs, err := fs.Sub(fsEmbed, targetPath)
	if err != nil {
		panic(err)
	}
	return embedFileSystem{
		FileSystem: http.FS(efs),
	}
}


================================================
FILE: common/env/helper.go
================================================
package env

import (
	"os"
	"strconv"
)

func Bool(env string, defaultValue bool) bool {
	if env == "" || os.Getenv(env) == "" {
		return defaultValue
	}
	return os.Getenv(env) == "true"
}

func Int(env string, defaultValue int) int {
	if env == "" || os.Getenv(env) == "" {
		return defaultValue
	}
	num, err := strconv.Atoi(os.Getenv(env))
	if err != nil {
		return defaultValue
	}
	return num
}

func Float64(env string, defaultValue float64) float64 {
	if env == "" || os.Getenv(env) == "" {
		return defaultValue
	}
	num, err := strconv.ParseFloat(os.Getenv(env), 64)
	if err != nil {
		return defaultValue
	}
	return num
}

func String(env string, defaultValue string) string {
	if env == "" || os.Getenv(env) == "" {
		return defaultValue
	}
	return os.Getenv(env)
}


================================================
FILE: common/gin.go
================================================
package common

import (
	"bytes"
	"encoding/json"
	"io"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common/ctxkey"
)

func GetRequestBody(c *gin.Context) ([]byte, error) {
	requestBody, _ := c.Get(ctxkey.KeyRequestBody)
	if requestBody != nil {
		return requestBody.([]byte), nil
	}
	requestBody, err := io.ReadAll(c.Request.Body)
	if err != nil {
		return nil, err
	}
	_ = c.Request.Body.Close()
	c.Set(ctxkey.KeyRequestBody, requestBody)
	return requestBody.([]byte), nil
}

func UnmarshalBodyReusable(c *gin.Context, v any) error {
	requestBody, err := GetRequestBody(c)
	if err != nil {
		return err
	}
	contentType := c.Request.Header.Get("Content-Type")
	if strings.HasPrefix(contentType, "application/json") {
		err = json.Unmarshal(requestBody, &v)
	} else {
		c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
		err = c.ShouldBind(&v)
	}
	if err != nil {
		return err
	}
	// Reset request body
	c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
	return nil
}

func SetEventStreamHeaders(c *gin.Context) {
	c.Writer.Header().Set("Content-Type", "text/event-stream")
	c.Writer.Header().Set("Cache-Control", "no-cache")
	c.Writer.Header().Set("Connection", "keep-alive")
	c.Writer.Header().Set("Transfer-Encoding", "chunked")
	c.Writer.Header().Set("X-Accel-Buffering", "no")
}


================================================
FILE: common/helper/helper.go
================================================
package helper

import (
	"context"
	"fmt"
	"html/template"
	"log"
	"net"
	"os/exec"
	"runtime"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/songquanpeng/one-api/common/random"
)

func OpenBrowser(url string) {
	var err error

	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", url).Start()
	case "windows":
		err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
	case "darwin":
		err = exec.Command("open", url).Start()
	}
	if err != nil {
		log.Println(err)
	}
}

func GetIp() (ip string) {
	ips, err := net.InterfaceAddrs()
	if err != nil {
		log.Println(err)
		return ip
	}

	for _, a := range ips {
		if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
			if ipNet.IP.To4() != nil {
				ip = ipNet.IP.String()
				if strings.HasPrefix(ip, "10") {
					return
				}
				if strings.HasPrefix(ip, "172") {
					return
				}
				if strings.HasPrefix(ip, "192.168") {
					return
				}
				ip = ""
			}
		}
	}
	return
}

var sizeKB = 1024
var sizeMB = sizeKB * 1024
var sizeGB = sizeMB * 1024

func Bytes2Size(num int64) string {
	numStr := ""
	unit := "B"
	if num/int64(sizeGB) > 1 {
		numStr = fmt.Sprintf("%.2f", float64(num)/float64(sizeGB))
		unit = "GB"
	} else if num/int64(sizeMB) > 1 {
		numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeMB)))
		unit = "MB"
	} else if num/int64(sizeKB) > 1 {
		numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeKB)))
		unit = "KB"
	} else {
		numStr = fmt.Sprintf("%d", num)
	}
	return numStr + " " + unit
}

func Interface2String(inter interface{}) string {
	switch inter := inter.(type) {
	case string:
		return inter
	case int:
		return fmt.Sprintf("%d", inter)
	case float64:
		return fmt.Sprintf("%f", inter)
	}
	return "Not Implemented"
}

func UnescapeHTML(x string) interface{} {
	return template.HTML(x)
}

func IntMax(a int, b int) int {
	if a >= b {
		return a
	} else {
		return b
	}
}

func GenRequestID() string {
	return GetTimeString() + random.GetRandomNumberString(8)
}

func SetRequestID(ctx context.Context, id string) context.Context {
	return context.WithValue(ctx, RequestIdKey, id)
}

func GetRequestID(ctx context.Context) string {
	rawRequestId := ctx.Value(RequestIdKey)
	if rawRequestId == nil {
		return ""
	}
	return rawRequestId.(string)
}

func GetResponseID(c *gin.Context) string {
	logID := c.GetString(RequestIdKey)
	return fmt.Sprintf("chatcmpl-%s", logID)
}

func Max(a int, b int) int {
	if a >= b {
		return a
	} else {
		return b
	}
}

func AssignOrDefault(value string, defaultValue string) string {
	if len(value) != 0 {
		return value
	}
	return defaultValue
}

func MessageWithRequestId(message string, id string) string {
	return fmt.Sprintf("%s (request id: %s)", message, id)
}

func String2Int(str string) int {
	num, err := strconv.Atoi(str)
	if err != nil {
		return 0
	}
	return num
}

func Float64PtrMax(p *float64, maxValue float64) *float64 {
	if p == nil {
		return nil
	}
	if *p > maxValue {
		return &maxValue
	}
	return p
}

func Float64PtrMin(p *float64, minValue float64) *float64 {
	if p == nil {
		return nil
	}
	if *p < minValue {
		return &minValue
	}
	return p
}


================================================
FILE: common/helper/key.go
================================================
package helper

const (
	RequestIdKey = "X-Oneapi-Request-Id"
)


================================================
FILE: common/helper/time.go
================================================
package helper

import (
	"fmt"
	"time"
)

func GetTimestamp() int64 {
	return time.Now().Unix()
}

func GetTimeString() string {
	now := time.Now()
	return fmt.Sprintf("%s%d", now.Format("20060102150405"), now.UnixNano()%1e9)
}

// CalcElapsedTime return the elapsed time in milliseconds (ms)
func CalcElapsedTime(start time.Time) int64 {
	return time.Now().Sub(start).Milliseconds()
}


================================================
FILE: common/i18n/i18n.go
================================================
package i18n

import (
	"embed"
	"encoding/json"
	"strings"

	"github.com/gin-gonic/gin"
)

//go:embed locales/*.json
var localesFS embed.FS

var (
	translations = make(map[string]map[string]string)
	defaultLang  = "en"
	ContextKey   = "i18n"
)

// Init loads all translation files from embedded filesystem
func Init() error {
	entries, err := localesFS.ReadDir("locales")
	if err != nil {
		return err
	}

	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
			continue
		}

		langCode := strings.TrimSuffix(entry.Name(), ".json")
		content, err := localesFS.ReadFile("locales/" + entry.Name())
		if err != nil {
			return err
		}

		var translation map[string]string
		if err := json.Unmarshal(content, &translation); err != nil {
			return err
		}
		translations[langCode] = translation
	}

	return nil
}

func GetLang(c *gin.Context) string {
	rawLang, ok := c.Get(ContextKey)
	if !ok {
		return defaultLang
	}
	lang, _ := rawLang.(string)
	if lang != "" {
		return lang
	}
	return defaultLang
}

func Translate(c *gin.Context, message string) string {
	lang := GetLang(c)
	return translateHelper(lang, message)
}

func translateHelper(lang, message string) string {
	if trans, ok := translations[lang]; ok {
		if translated, exists := trans[message]; exists {
			return translated
		}
	}
	return message
}


================================================
FILE: common/i18n/locales/en.json
================================================
{
  "invalid_input": "Invalid input, please check your input",
  "send_email_failed": "failed to send email: ",
  "invalid_parameter": "invalid parameter"
}


================================================
FILE: common/i18n/locales/zh-CN.json
================================================
{
  "invalid_input": "无效的输入,请检查您的输入",
  "send_email_failed": "发送邮件失败:",
  "invalid_parameter": "无效的参数"
}


================================================
FILE: common/image/image.go
================================================
package image

import (
	"bytes"
	"encoding/base64"
	"github.com/songquanpeng/one-api/common/client"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"net/http"
	"regexp"
	"strings"
	"sync"

	_ "golang.org/x/image/webp"
)

// Regex to match data URL pattern
var dataURLPattern = regexp.MustCompile(`data:image/([^;]+);base64,(.*)`)

func IsImageUrl(url string) (bool, error) {
	resp, err := client.UserContentRequestHTTPClient.Head(url)
	if err != nil {
		return false, err
	}
	if !strings.HasPrefix(resp.Header.Get("Content-Type"), "image/") {
		return false, nil
	}
	return true, nil
}

func GetImageSizeFromUrl(url string) (width int, height int, err error) {
	isImage, err := IsImageUrl(url)
	if !isImage {
		return
	}
	resp, err := client.UserContentRequestHTTPClient.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	img, _, err := image.DecodeConfig(resp.Body)
	if err != nil {
		return
	}
	return img.Width, img.Height, nil
}

func GetImageFromUrl(url string) (mimeType string, data string, err error) {
	// Check if the URL is a data URL
	matches := dataURLPattern.FindStringSubmatch(url)
	if len(matches) == 3 {
		// URL is a data URL
		mimeType = "image/" + matches[1]
		data = matches[2]
		return
	}

	isImage, err := IsImageUrl(url)
	if !isImage {
		return
	}
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	buffer := bytes.NewBuffer(nil)
	_, err = buffer.ReadFrom(resp.Body)
	if err != nil {
		return
	}
	mimeType = resp.Header.Get("Content-Type")
	data = base64.StdEncoding.EncodeToString(buffer.Bytes())
	return
}

var (
	reg = regexp.MustCompile(`data:image/([^;]+);base64,`)
)

var readerPool = sync.Pool{
	New: func() interface{} {
		return &bytes.Reader{}
	},
}

func GetImageSizeFromBase64(encoded string) (width int, height int, err error) {
	decoded, err := base64.StdEncoding.DecodeString(reg.ReplaceAllString(encoded, ""))
	if err != nil {
		return 0, 0, err
	}

	reader := readerPool.Get().(*bytes.Reader)
	defer readerPool.Put(reader)
	reader.Reset(decoded)

	img, _, err := image.DecodeConfig(reader)
	if err != nil {
		return 0, 0, err
	}

	return img.Width, img.Height, nil
}

func GetImageSize(image string) (width int, height int, err error) {
	if strings.HasPrefix(image, "data:image/") {
		return GetImageSizeFromBase64(image)
	}
	return GetImageSizeFromUrl(image)
}


================================================
FILE: common/image/image_test.go
================================================
package image_test

import (
	"encoding/base64"
	"github.com/songquanpeng/one-api/common/client"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"net/http"
	"strconv"
	"strings"
	"testing"

	img "github.com/songquanpeng/one-api/common/image"

	"github.com/stretchr/testify/assert"
	_ "golang.org/x/image/webp"
)

type CountingReader struct {
	reader    io.Reader
	BytesRead int
}

func (r *CountingReader) Read(p []byte) (n int, err error) {
	n, err = r.reader.Read(p)
	r.BytesRead += n
	return n, err
}

var (
	cases = []struct {
		url    string
		format string
		width  int
		height int
	}{
		{"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", "jpeg", 2560, 1669},
		{"https://upload.wikimedia.org/wikipedia/commons/9/97/Basshunter_live_performances.png", "png", 4500, 2592},
		{"https://upload.wikimedia.org/wikipedia/commons/c/c6/TO_THE_ONE_SOMETHINGNESS.webp", "webp", 984, 985},
		{"https://upload.wikimedia.org/wikipedia/commons/d/d0/01_Das_Sandberg-Modell.gif", "gif", 1917, 1533},
		{"https://upload.wikimedia.org/wikipedia/commons/6/62/102Cervus.jpg", "jpeg", 270, 230},
	}
)

func TestMain(m *testing.M) {
	client.Init()
	m.Run()
}

func TestDecode(t *testing.T) {
	// Bytes read: varies sometimes
	// jpeg: 1063892
	// png: 294462
	// webp: 99529
	// gif: 956153
	// jpeg#01: 32805
	for _, c := range cases {
		t.Run("Decode:"+c.format, func(t *testing.T) {
			resp, err := http.Get(c.url)
			assert.NoError(t, err)
			defer resp.Body.Close()
			reader := &CountingReader{reader: resp.Body}
			img, format, err := image.Decode(reader)
			assert.NoError(t, err)
			size := img.Bounds().Size()
			assert.Equal(t, c.format, format)
			assert.Equal(t, c.width, size.X)
			assert.Equal(t, c.height, size.Y)
			t.Logf("Bytes read: %d", reader.BytesRead)
		})
	}

	// Bytes read:
	// jpeg: 4096
	// png: 4096
	// webp: 4096
	// gif: 4096
	// jpeg#01: 4096
	for _, c := range cases {
		t.Run("DecodeConfig:"+c.format, func(t *testing.T) {
			resp, err := http.Get(c.url)
			assert.NoError(t, err)
			defer resp.Body.Close()
			reader := &CountingReader{reader: resp.Body}
			config, format, err := image.DecodeConfig(reader)
			assert.NoError(t, err)
			assert.Equal(t, c.format, format)
			assert.Equal(t, c.width, config.Width)
			assert.Equal(t, c.height, config.Height)
			t.Logf("Bytes read: %d", reader.BytesRead)
		})
	}
}

func TestBase64(t *testing.T) {
	// Bytes read:
	// jpeg: 1063892
	// png: 294462
	// webp: 99072
	// gif: 953856
	// jpeg#01: 32805
	for _, c := range cases {
		t.Run("Decode:"+c.format, func(t *testing.T) {
			resp, err := http.Get(c.url)
			assert.NoError(t, err)
			defer resp.Body.Close()
			data, err := io.ReadAll(resp.Body)
			assert.NoError(t, err)
			encoded := base64.StdEncoding.EncodeToString(data)
			body := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encoded))
			reader := &CountingReader{reader: body}
			img, format, err := image.Decode(reader)
			assert.NoError(t, err)
			size := img.Bounds().Size()
			assert.Equal(t, c.format, format)
			assert.Equal(t, c.width, size.X)
			assert.Equal(t, c.height, size.Y)
			t.Logf("Bytes read: %d", reader.BytesRead)
		})
	}

	// Bytes read:
	// jpeg: 1536
	// png: 768
	// webp: 768
	// gif: 1536
	// jpeg#01: 3840
	for _, c := range cases {
		t.Run("DecodeConfig:"+c.format, func(t *testing.T) {
			resp, err := http.Get(c.url)
			assert.NoError(t, err)
			defer resp.Body.Close()
			data, err := io.ReadAll(resp.Body)
			assert.NoError(t, err)
			encoded := base64.StdEncoding.EncodeToString(data)
			body := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encoded))
			reader := &CountingReader{reader: body}
			config, format, err := image.DecodeConfig(reader)
			assert.NoError(t, err)
			assert.Equal(t, c.format, format)
			assert.Equal(t, c.width, config.Width)
			assert.Equal(t, c.height, config.Height)
			t.Logf("Bytes read: %d", reader.BytesRead)
		})
	}
}

func TestGetImageSize(t *testing.T) {
	for i, c := range cases {
		t.Run("Decode:"+strconv.Itoa(i), func(t *testing.T) {
			width, height, err := img.GetImageSize(c.url)
			assert.NoError(t, err)
			assert.Equal(t, c.width, width)
			assert.Equal(t, c.height, height)
		})
	}
}

func TestGetImageSizeFromBase64(t *testing.T) {
	for i, c := range cases {
		t.Run("Decode:"+strconv.Itoa(i), func(t *testing.T) {
			resp, err := http.Get(c.url)
			assert.NoError(t, err)
			defer resp.Body.Close()
			data, err := io.ReadAll(resp.Body)
			assert.NoError(t, err)
			encoded := base64.StdEncoding.EncodeToString(data)
			width, height, err := img.GetImageSizeFromBase64(encoded)
			assert.NoError(t, err)
			assert.Equal(t, c.width, width)
			assert.Equal(t, c.height, height)
		})
	}
}


================================================
FILE: common/init.go
================================================
package common

import (
	"flag"
	"fmt"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/logger"
	"log"
	"os"
	"path/filepath"
)

var (
	Port         = flag.Int("port", 3000, "the listening port")
	PrintVersion = flag.Bool("version", false, "print version and exit")
	PrintHelp    = flag.Bool("help", false, "print help and exit")
	LogDir       = flag.String("log-dir", "./logs", "specify the log directory")
)

func printHelp() {
	fmt.Println("One API " + Version + " - All in one API service for OpenAI API.")
	fmt.Println("Copyright (C) 2023 JustSong. All rights reserved.")
	fmt.Println("GitHub: https://github.com/songquanpeng/one-api")
	fmt.Println("Usage: one-api [--port <port>] [--log-dir <log directory>] [--version] [--help]")
}

func Init() {
	flag.Parse()

	if *PrintVersion {
		fmt.Println(Version)
		os.Exit(0)
	}

	if *PrintHelp {
		printHelp()
		os.Exit(0)
	}

	if os.Getenv("SESSION_SECRET") != "" {
		if os.Getenv("SESSION_SECRET") == "random_string" {
			logger.SysError("SESSION_SECRET is set to an example value, please change it to a random string.")
		} else {
			config.SessionSecret = os.Getenv("SESSION_SECRET")
		}
	}
	if os.Getenv("SQLITE_PATH") != "" {
		SQLitePath = os.Getenv("SQLITE_PATH")
	}
	if *LogDir != "" {
		var err error
		*LogDir, err = filepath.Abs(*LogDir)
		if err != nil {
			log.Fatal(err)
		}
		if _, err := os.Stat(*LogDir); os.IsNotExist(err) {
			err = os.Mkdir(*LogDir, 0777)
			if err != nil {
				log.Fatal(err)
			}
		}
		logger.LogDir = *LogDir
	}
}


================================================
FILE: common/logger/constants.go
================================================
package logger

var LogDir string


================================================
FILE: common/logger/logger.go
================================================
package logger

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/helper"
)

type loggerLevel string

const (
	loggerDEBUG loggerLevel = "DEBUG"
	loggerINFO  loggerLevel = "INFO"
	loggerWarn  loggerLevel = "WARN"
	loggerError loggerLevel = "ERROR"
	loggerFatal loggerLevel = "FATAL"
)

var setupLogOnce sync.Once

func SetupLogger() {
	setupLogOnce.Do(func() {
		if LogDir != "" {
			var logPath string
			if config.OnlyOneLogFile {
				logPath = filepath.Join(LogDir, "oneapi.log")
			} else {
				logPath = filepath.Join(LogDir, fmt.Sprintf("oneapi-%s.log", time.Now().Format("20060102")))
			}
			fd, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				log.Fatal("failed to open log file")
			}
			gin.DefaultWriter = io.MultiWriter(os.Stdout, fd)
			gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, fd)
		}
	})
}

func SysLog(s string) {
	logHelper(nil, loggerINFO, s)
}

func SysLogf(format string, a ...any) {
	logHelper(nil, loggerINFO, fmt.Sprintf(format, a...))
}

func SysWarn(s string) {
	logHelper(nil, loggerWarn, s)
}

func SysWarnf(format string, a ...any) {
	logHelper(nil, loggerWarn, fmt.Sprintf(format, a...))
}

func SysError(s string) {
	logHelper(nil, loggerError, s)
}

func SysErrorf(format string, a ...any) {
	logHelper(nil, loggerError, fmt.Sprintf(format, a...))
}

func Debug(ctx context.Context, msg string) {
	if !config.DebugEnabled {
		return
	}
	logHelper(ctx, loggerDEBUG, msg)
}

func Info(ctx context.Context, msg string) {
	logHelper(ctx, loggerINFO, msg)
}

func Warn(ctx context.Context, msg string) {
	logHelper(ctx, loggerWarn, msg)
}

func Error(ctx context.Context, msg string) {
	logHelper(ctx, loggerError, msg)
}

func Debugf(ctx context.Context, format string, a ...any) {
	if !config.DebugEnabled {
		return
	}
	logHelper(ctx, loggerDEBUG, fmt.Sprintf(format, a...))
}

func Infof(ctx context.Context, format string, a ...any) {
	logHelper(ctx, loggerINFO, fmt.Sprintf(format, a...))
}

func Warnf(ctx context.Context, format string, a ...any) {
	logHelper(ctx, loggerWarn, fmt.Sprintf(format, a...))
}

func Errorf(ctx context.Context, format string, a ...any) {
	logHelper(ctx, loggerError, fmt.Sprintf(format, a...))
}

func FatalLog(s string) {
	logHelper(nil, loggerFatal, s)
}

func FatalLogf(format string, a ...any) {
	logHelper(nil, loggerFatal, fmt.Sprintf(format, a...))
}

func logHelper(ctx context.Context, level loggerLevel, msg string) {
	writer := gin.DefaultErrorWriter
	if level == loggerINFO {
		writer = gin.DefaultWriter
	}
	var requestId string
	if ctx != nil {
		rawRequestId := helper.GetRequestID(ctx)
		if rawRequestId != "" {
			requestId = fmt.Sprintf(" | %s", rawRequestId)
		}
	}
	lineInfo, funcName := getLineInfo()
	now := time.Now()
	_, _ = fmt.Fprintf(writer, "[%s] %v%s%s %s%s \n", level, now.Format("2006/01/02 - 15:04:05"), requestId, lineInfo, funcName, msg)
	SetupLogger()
	if level == loggerFatal {
		os.Exit(1)
	}
}

func getLineInfo() (string, string) {
	funcName := "[unknown] "
	pc, file, line, ok := runtime.Caller(3)
	if ok {
		if fn := runtime.FuncForPC(pc); fn != nil {
			parts := strings.Split(fn.Name(), ".")
			funcName = "[" + parts[len(parts)-1] + "] "
		}
	} else {
		file = "unknown"
		line = 0
	}
	parts := strings.Split(file, "one-api/")
	if len(parts) > 1 {
		file = parts[1]
	}
	return fmt.Sprintf(" | %s:%d", file, line), funcName
}


================================================
FILE: common/message/email.go
================================================
package message

import (
	"crypto/rand"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"net"
	"net/smtp"
	"strings"
	"time"

	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/logger"
)

func shouldAuth() bool {
	return config.SMTPAccount != "" || config.SMTPToken != ""
}

func SendEmail(subject string, receiver string, content string) error {
	if receiver == "" {
		return fmt.Errorf("receiver is empty")
	}
	if config.SMTPFrom == "" { // for compatibility
		config.SMTPFrom = config.SMTPAccount
	}
	encodedSubject := fmt.Sprintf("=?UTF-8?B?%s?=", base64.StdEncoding.EncodeToString([]byte(subject)))

	// Extract domain from SMTPFrom
	parts := strings.Split(config.SMTPFrom, "@")
	var domain string
	if len(parts) > 1 {
		domain = parts[1]
	}
	// Generate a unique Message-ID
	buf := make([]byte, 16)
	_, err := rand.Read(buf)
	if err != nil {
		return err
	}
	messageId := fmt.Sprintf("<%x@%s>", buf, domain)

	mail := []byte(fmt.Sprintf("To: %s\r\n"+
		"From: %s<%s>\r\n"+
		"Subject: %s\r\n"+
		"Message-ID: %s\r\n"+ // add Message-ID header to avoid being treated as spam, RFC 5322
		"Date: %s\r\n"+
		"Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n",
		receiver, config.SystemName, config.SMTPFrom, encodedSubject, messageId, time.Now().Format(time.RFC1123Z), content))

	auth := smtp.PlainAuth("", config.SMTPAccount, config.SMTPToken, config.SMTPServer)
	addr := fmt.Sprintf("%s:%d", config.SMTPServer, config.SMTPPort)
	to := strings.Split(receiver, ";")

	if config.SMTPPort == 465 || !shouldAuth() {
		// need advanced client
		var conn net.Conn
		var err error
		if config.SMTPPort == 465 {
			tlsConfig := &tls.Config{
				InsecureSkipVerify: true,
				ServerName:         config.SMTPServer,
			}
			conn, err = tls.Dial("tcp", fmt.Sprintf("%s:%d", config.SMTPServer, config.SMTPPort), tlsConfig)
		} else {
			conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", config.SMTPServer, config.SMTPPort))
		}
		if err != nil {
			return err
		}
		client, err := smtp.NewClient(conn, config.SMTPServer)
		if err != nil {
			return err
		}
		defer client.Close()
		if shouldAuth() {
			if err = client.Auth(auth); err != nil {
				return err
			}
		}
		if err = client.Mail(config.SMTPFrom); err != nil {
			return err
		}
		receiverEmails := strings.Split(receiver, ";")
		for _, receiver := range receiverEmails {
			if err = client.Rcpt(receiver); err != nil {
				return err
			}
		}
		w, err := client.Data()
		if err != nil {
			return err
		}
		_, err = w.Write(mail)
		if err != nil {
			return err
		}
		err = w.Close()
		if err != nil {
			return err
		}
		return nil
	}
	err = smtp.SendMail(addr, auth, config.SMTPAccount, to, mail)
	if err != nil && strings.Contains(err.Error(), "short response") { // 部分提供商返回该错误,但实际上邮件已经发送成功
		logger.SysWarnf("short response from SMTP server, return nil instead of error: %s", err.Error())
		return nil
	}
	return err
}


================================================
FILE: common/message/main.go
================================================
package message

import (
	"fmt"
	"github.com/songquanpeng/one-api/common/config"
)

const (
	ByAll           = "all"
	ByEmail         = "email"
	ByMessagePusher = "message_pusher"
)

func Notify(by string, title string, description string, content string) error {
	if by == ByEmail {
		return SendEmail(title, config.RootUserEmail, content)
	}
	if by == ByMessagePusher {
		return SendMessage(title, description, content)
	}
	return fmt.Errorf("unknown notify method: %s", by)
}


================================================
FILE: common/message/message-pusher.go
================================================
package message

import (
	"bytes"
	"encoding/json"
	"errors"
	"github.com/songquanpeng/one-api/common/config"
	"net/http"
)

type request struct {
	Title       string `json:"title"`
	Description string `json:"description"`
	Content     string `json:"content"`
	URL         string `json:"url"`
	Channel     string `json:"channel"`
	Token       string `json:"token"`
}

type response struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

func SendMessage(title string, description string, content string) error {
	if config.MessagePusherAddress == "" {
		return errors.New("message pusher address is not set")
	}
	req := request{
		Title:       title,
		Description: description,
		Content:     content,
		Token:       config.MessagePusherToken,
	}
	data, err := json.Marshal(req)
	if err != nil {
		return err
	}
	resp, err := http.Post(config.MessagePusherAddress,
		"application/json", bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	var res response
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	if !res.Success {
		return errors.New(res.Message)
	}
	return nil
}


================================================
FILE: common/message/template.go
================================================
package message

import (
	"fmt"

	"github.com/songquanpeng/one-api/common/config"
)

// EmailTemplate 生成美观的 HTML 邮件内容
func EmailTemplate(title, content string) string {
	return fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: 0; padding: 20px; font-family: Arial, sans-serif; line-height: 1.6; background-color: #f4f4f4;">
    <div style="max-width: 600px; margin: 20px auto; padding: 30px; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
        <div style="text-align: center; margin-bottom: 30px;">
            <h2 style="color: #333; margin: 0; font-size: 24px;">%s</h2>
        </div>
        <div style="color: #555; font-size: 16px;">
            %s
        </div>
        <div style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; color: #888; font-size: 14px; text-align: center;">
            <p style="margin: 5px 0;">此邮件由系统自动发送,请勿直接回复</p>
            <p style="margin: 5px 0;">%s</p>
        </div>
    </div>
</body>
</html>
`, title, content, config.SystemName)
}


================================================
FILE: common/network/ip.go
================================================
package network

import (
	"context"
	"fmt"
	"github.com/songquanpeng/one-api/common/logger"
	"net"
	"strings"
)

func splitSubnets(subnets string) []string {
	res := strings.Split(subnets, ",")
	for i := 0; i < len(res); i++ {
		res[i] = strings.TrimSpace(res[i])
	}
	return res
}

func isValidSubnet(subnet string) error {
	_, _, err := net.ParseCIDR(subnet)
	if err != nil {
		return fmt.Errorf("failed to parse subnet: %w", err)
	}
	return nil
}

func isIpInSubnet(ctx context.Context, ip string, subnet string) bool {
	_, ipNet, err := net.ParseCIDR(subnet)
	if err != nil {
		logger.Errorf(ctx, "failed to parse subnet: %s", err.Error())
		return false
	}
	return ipNet.Contains(net.ParseIP(ip))
}

func IsValidSubnets(subnets string) error {
	for _, subnet := range splitSubnets(subnets) {
		if err := isValidSubnet(subnet); err != nil {
			return err
		}
	}
	return nil
}

func IsIpInSubnets(ctx context.Context, ip string, subnets string) bool {
	for _, subnet := range splitSubnets(subnets) {
		if isIpInSubnet(ctx, ip, subnet) {
			return true
		}
	}
	return false
}


================================================
FILE: common/network/ip_test.go
================================================
package network

import (
	"context"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestIsIpInSubnet(t *testing.T) {
	ctx := context.Background()
	ip1 := "192.168.0.5"
	ip2 := "125.216.250.89"
	subnet := "192.168.0.0/24"
	Convey("TestIsIpInSubnet", t, func() {
		So(isIpInSubnet(ctx, ip1, subnet), ShouldBeTrue)
		So(isIpInSubnet(ctx, ip2, subnet), ShouldBeFalse)
	})
}


================================================
FILE: common/random/main.go
================================================
package random

import (
	"github.com/google/uuid"
	"math/rand"
	"strings"
	"time"
)

func GetUUID() string {
	code := uuid.New().String()
	code = strings.Replace(code, "-", "", -1)
	return code
}

const keyChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const keyNumbers = "0123456789"

func init() {
	rand.Seed(time.Now().UnixNano())
}

func GenerateKey() string {
	rand.Seed(time.Now().UnixNano())
	key := make([]byte, 48)
	for i := 0; i < 16; i++ {
		key[i] = keyChars[rand.Intn(len(keyChars))]
	}
	uuid_ := GetUUID()
	for i := 0; i < 32; i++ {
		c := uuid_[i]
		if i%2 == 0 && c >= 'a' && c <= 'z' {
			c = c - 'a' + 'A'
		}
		key[i+16] = c
	}
	return string(key)
}

func GetRandomString(length int) string {
	rand.Seed(time.Now().UnixNano())
	key := make([]byte, length)
	for i := 0; i < length; i++ {
		key[i] = keyChars[rand.Intn(len(keyChars))]
	}
	return string(key)
}

func GetRandomNumberString(length int) string {
	rand.Seed(time.Now().UnixNano())
	key := make([]byte, length)
	for i := 0; i < length; i++ {
		key[i] = keyNumbers[rand.Intn(len(keyNumbers))]
	}
	return string(key)
}

// RandRange returns a random number between min and max (max is not included)
func RandRange(min, max int) int {
	return min + rand.Intn(max-min)
}


================================================
FILE: common/rate-limit.go
================================================
package common

import (
	"sync"
	"time"
)

type InMemoryRateLimiter struct {
	store              map[string]*[]int64
	mutex              sync.Mutex
	expirationDuration time.Duration
}

func (l *InMemoryRateLimiter) Init(expirationDuration time.Duration) {
	if l.store == nil {
		l.mutex.Lock()
		if l.store == nil {
			l.store = make(map[string]*[]int64)
			l.expirationDuration = expirationDuration
			if expirationDuration > 0 {
				go l.clearExpiredItems()
			}
		}
		l.mutex.Unlock()
	}
}

func (l *InMemoryRateLimiter) clearExpiredItems() {
	for {
		time.Sleep(l.expirationDuration)
		l.mutex.Lock()
		now := time.Now().Unix()
		for key := range l.store {
			queue := l.store[key]
			size := len(*queue)
			if size == 0 || now-(*queue)[size-1] > int64(l.expirationDuration.Seconds()) {
				delete(l.store, key)
			}
		}
		l.mutex.Unlock()
	}
}

// Request parameter duration's unit is seconds
func (l *InMemoryRateLimiter) Request(key string, maxRequestNum int, duration int64) bool {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	// [old <-- new]
	queue, ok := l.store[key]
	now := time.Now().Unix()
	if ok {
		if len(*queue) < maxRequestNum {
			*queue = append(*queue, now)
			return true
		} else {
			if now-(*queue)[0] >= duration {
				*queue = (*queue)[1:]
				*queue = append(*queue, now)
				return true
			} else {
				return false
			}
		}
	} else {
		s := make([]int64, 0, maxRequestNum)
		l.store[key] = &s
		*(l.store[key]) = append(*(l.store[key]), now)
	}
	return true
}


================================================
FILE: common/redis.go
================================================
package common

import (
	"context"
	"os"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/songquanpeng/one-api/common/logger"
)

var RDB redis.Cmdable
var RedisEnabled = true

// InitRedisClient This function is called after init()
func InitRedisClient() (err error) {
	if os.Getenv("REDIS_CONN_STRING") == "" {
		RedisEnabled = false
		logger.SysLog("REDIS_CONN_STRING not set, Redis is not enabled")
		return nil
	}
	if os.Getenv("SYNC_FREQUENCY") == "" {
		RedisEnabled = false
		logger.SysLog("SYNC_FREQUENCY not set, Redis is disabled")
		return nil
	}
	redisConnString := os.Getenv("REDIS_CONN_STRING")
	if os.Getenv("REDIS_MASTER_NAME") == "" {
		logger.SysLog("Redis is enabled")
		opt, err := redis.ParseURL(redisConnString)
		if err != nil {
			logger.FatalLog("failed to parse Redis connection string: " + err.Error())
		}
		RDB = redis.NewClient(opt)
	} else {
		// cluster mode
		logger.SysLog("Redis cluster mode enabled")
		RDB = redis.NewUniversalClient(&redis.UniversalOptions{
			Addrs:      strings.Split(redisConnString, ","),
			Password:   os.Getenv("REDIS_PASSWORD"),
			MasterName: os.Getenv("REDIS_MASTER_NAME"),
		})
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	_, err = RDB.Ping(ctx).Result()
	if err != nil {
		logger.FatalLog("Redis ping test failed: " + err.Error())
	}
	return err
}

func ParseRedisOption() *redis.Options {
	opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING"))
	if err != nil {
		logger.FatalLog("failed to parse Redis connection string: " + err.Error())
	}
	return opt
}

func RedisSet(key string, value string, expiration time.Duration) error {
	ctx := context.Background()
	return RDB.Set(ctx, key, value, expiration).Err()
}

func RedisGet(key string) (string, error) {
	ctx := context.Background()
	return RDB.Get(ctx, key).Result()
}

func RedisDel(key string) error {
	ctx := context.Background()
	return RDB.Del(ctx, key).Err()
}

func RedisDecrease(key string, value int64) error {
	ctx := context.Background()
	return RDB.DecrBy(ctx, key, value).Err()
}


================================================
FILE: common/render/render.go
================================================
package render

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common"
)

func StringData(c *gin.Context, str string) {
	str = strings.TrimPrefix(str, "data: ")
	str = strings.TrimSuffix(str, "\r")
	c.Render(-1, common.CustomEvent{Data: "data: " + str})
	c.Writer.Flush()
}

func ObjectData(c *gin.Context, object interface{}) error {
	jsonData, err := json.Marshal(object)
	if err != nil {
		return fmt.Errorf("error marshalling object: %w", err)
	}
	StringData(c, string(jsonData))
	return nil
}

func Done(c *gin.Context) {
	StringData(c, "[DONE]")
}


================================================
FILE: common/utils/array.go
================================================
package utils

func DeDuplication(slice []string) []string {
	m := make(map[string]bool)
	for _, v := range slice {
		m[v] = true
	}
	result := make([]string, 0, len(m))
	for v := range m {
		result = append(result, v)
	}
	return result
}


================================================
FILE: common/utils.go
================================================
package common

import (
	"fmt"
	"github.com/songquanpeng/one-api/common/config"
)

func LogQuota(quota int64) string {
	if config.DisplayInCurrencyEnabled {
		return fmt.Sprintf("$%.6f 额度", float64(quota)/config.QuotaPerUnit)
	} else {
		return fmt.Sprintf("%d 点额度", quota)
	}
}


================================================
FILE: common/validate.go
================================================
package common

import "github.com/go-playground/validator/v10"

var Validate *validator.Validate

func init() {
	Validate = validator.New()
}


================================================
FILE: common/verification.go
================================================
package common

import (
	"github.com/google/uuid"
	"strings"
	"sync"
	"time"
)

type verificationValue struct {
	code string
	time time.Time
}

const (
	EmailVerificationPurpose = "v"
	PasswordResetPurpose     = "r"
)

var verificationMutex sync.Mutex
var verificationMap map[string]verificationValue
var verificationMapMaxSize = 10
var VerificationValidMinutes = 10

func GenerateVerificationCode(length int) string {
	code := uuid.New().String()
	code = strings.Replace(code, "-", "", -1)
	if length == 0 {
		return code
	}
	return code[:length]
}

func RegisterVerificationCodeWithKey(key string, code string, purpose string) {
	verificationMutex.Lock()
	defer verificationMutex.Unlock()
	verificationMap[purpose+key] = verificationValue{
		code: code,
		time: time.Now(),
	}
	if len(verificationMap) > verificationMapMaxSize {
		removeExpiredPairs()
	}
}

func VerifyCodeWithKey(key string, code string, purpose string) bool {
	verificationMutex.Lock()
	defer verificationMutex.Unlock()
	value, okay := verificationMap[purpose+key]
	now := time.Now()
	if !okay || int(now.Sub(value.time).Seconds()) >= VerificationValidMinutes*60 {
		return false
	}
	return code == value.code
}

func DeleteKey(key string, purpose string) {
	verificationMutex.Lock()
	defer verificationMutex.Unlock()
	delete(verificationMap, purpose+key)
}

// no lock inside, so the caller must lock the verificationMap before calling!
func removeExpiredPairs() {
	now := time.Now()
	for key := range verificationMap {
		if int(now.Sub(verificationMap[key].time).Seconds()) >= VerificationValidMinutes*60 {
			delete(verificationMap, key)
		}
	}
}

func init() {
	verificationMutex.Lock()
	defer verificationMutex.Unlock()
	verificationMap = make(map[string]verificationValue)
}


================================================
FILE: controller/auth/github.go
================================================
package auth

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"

	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/logger"
	"github.com/songquanpeng/one-api/common/random"
	"github.com/songquanpeng/one-api/controller"
	"github.com/songquanpeng/one-api/model"
)

type GitHubOAuthResponse struct {
	AccessToken string `json:"access_token"`
	Scope       string `json:"scope"`
	TokenType   string `json:"token_type"`
}

type GitHubUser struct {
	Login string `json:"login"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
	if code == "" {
		return nil, errors.New("无效的参数")
	}
	values := map[string]string{"client_id": config.GitHubClientId, "client_secret": config.GitHubClientSecret, "code": code}
	jsonData, err := json.Marshal(values)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", "https://github.com/login/oauth/access_token", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	client := http.Client{
		Timeout: 5 * time.Second,
	}
	res, err := client.Do(req)
	if err != nil {
		logger.SysLog(err.Error())
		return nil, errors.New("无法连接至 GitHub 服务器,请稍后重试!")
	}
	defer res.Body.Close()
	var oAuthResponse GitHubOAuthResponse
	err = json.NewDecoder(res.Body).Decode(&oAuthResponse)
	if err != nil {
		return nil, err
	}
	req, err = http.NewRequest("GET", "https://api.github.com/user", nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", oAuthResponse.AccessToken))
	res2, err := client.Do(req)
	if err != nil {
		logger.SysLog(err.Error())
		return nil, errors.New("无法连接至 GitHub 服务器,请稍后重试!")
	}
	defer res2.Body.Close()
	var githubUser GitHubUser
	err = json.NewDecoder(res2.Body).Decode(&githubUser)
	if err != nil {
		return nil, err
	}
	if githubUser.Login == "" {
		return nil, errors.New("返回值非法,用户字段为空,请稍后重试!")
	}
	return &githubUser, nil
}

func GitHubOAuth(c *gin.Context) {
	ctx := c.Request.Context()
	session := sessions.Default(c)
	state := c.Query("state")
	if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
		c.JSON(http.StatusForbidden, gin.H{
			"success": false,
			"message": "state is empty or not same",
		})
		return
	}
	username := session.Get("username")
	if username != nil {
		GitHubBind(c)
		return
	}

	if !config.GitHubOAuthEnabled {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "管理员未开启通过 GitHub 登录以及注册",
		})
		return
	}
	code := c.Query("code")
	githubUser, err := getGitHubUserInfoByCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user := model.User{
		GitHubId: githubUser.Login,
	}
	if model.IsGitHubIdAlreadyTaken(user.GitHubId) {
		err := user.FillUserByGitHubId()
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": err.Error(),
			})
			return
		}
	} else {
		if config.RegisterEnabled {
			user.Username = "github_" + strconv.Itoa(model.GetMaxUserId()+1)
			if githubUser.Name != "" {
				user.DisplayName = githubUser.Name
			} else {
				user.DisplayName = "GitHub User"
			}
			user.Email = githubUser.Email
			user.Role = model.RoleCommonUser
			user.Status = model.UserStatusEnabled

			if err := user.Insert(ctx, 0); err != nil {
				c.JSON(http.StatusOK, gin.H{
					"success": false,
					"message": err.Error(),
				})
				return
			}
		} else {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "管理员关闭了新用户注册",
			})
			return
		}
	}

	if user.Status != model.UserStatusEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "用户已被封禁",
			"success": false,
		})
		return
	}
	controller.SetupLogin(&user, c)
}

func GitHubBind(c *gin.Context) {
	if !config.GitHubOAuthEnabled {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "管理员未开启通过 GitHub 登录以及注册",
		})
		return
	}
	code := c.Query("code")
	githubUser, err := getGitHubUserInfoByCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user := model.User{
		GitHubId: githubUser.Login,
	}
	if model.IsGitHubIdAlreadyTaken(user.GitHubId) {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "该 GitHub 账户已被绑定",
		})
		return
	}
	session := sessions.Default(c)
	id := session.Get("id")
	// id := c.GetInt("id")  // critical bug!
	user.Id = id.(int)
	err = user.FillUserById()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user.GitHubId = githubUser.Login
	err = user.Update(false)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "bind",
	})
	return
}

func GenerateOAuthCode(c *gin.Context) {
	session := sessions.Default(c)
	state := random.GetRandomString(12)
	session.Set("oauth_state", state)
	err := session.Save()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    state,
	})
}


================================================
FILE: controller/auth/lark.go
================================================
package auth

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"

	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/logger"
	"github.com/songquanpeng/one-api/controller"
	"github.com/songquanpeng/one-api/model"
)

type LarkOAuthResponse struct {
	AccessToken string `json:"access_token"`
}

type LarkUser struct {
	Name   string `json:"name"`
	OpenID string `json:"open_id"`
}

func getLarkUserInfoByCode(code string) (*LarkUser, error) {
	if code == "" {
		return nil, errors.New("无效的参数")
	}
	values := map[string]string{
		"client_id":     config.LarkClientId,
		"client_secret": config.LarkClientSecret,
		"code":          code,
		"grant_type":    "authorization_code",
		"redirect_uri":  fmt.Sprintf("%s/oauth/lark", config.ServerAddress),
	}
	jsonData, err := json.Marshal(values)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", "https://open.feishu.cn/open-apis/authen/v2/oauth/token", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	client := http.Client{
		Timeout: 5 * time.Second,
	}
	res, err := client.Do(req)
	if err != nil {
		logger.SysLog(err.Error())
		return nil, errors.New("无法连接至飞书服务器,请稍后重试!")
	}
	defer res.Body.Close()
	var oAuthResponse LarkOAuthResponse
	err = json.NewDecoder(res.Body).Decode(&oAuthResponse)
	if err != nil {
		return nil, err
	}
	req, err = http.NewRequest("GET", "https://passport.feishu.cn/suite/passport/oauth/userinfo", nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", oAuthResponse.AccessToken))
	res2, err := client.Do(req)
	if err != nil {
		logger.SysLog(err.Error())
		return nil, errors.New("无法连接至飞书服务器,请稍后重试!")
	}
	var larkUser LarkUser
	err = json.NewDecoder(res2.Body).Decode(&larkUser)
	if err != nil {
		return nil, err
	}
	return &larkUser, nil
}

func LarkOAuth(c *gin.Context) {
	ctx := c.Request.Context()
	session := sessions.Default(c)
	state := c.Query("state")
	if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
		c.JSON(http.StatusForbidden, gin.H{
			"success": false,
			"message": "state is empty or not same",
		})
		return
	}
	username := session.Get("username")
	if username != nil {
		LarkBind(c)
		return
	}
	code := c.Query("code")
	larkUser, err := getLarkUserInfoByCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user := model.User{
		LarkId: larkUser.OpenID,
	}
	if model.IsLarkIdAlreadyTaken(user.LarkId) {
		err := user.FillUserByLarkId()
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": err.Error(),
			})
			return
		}
	} else {
		if config.RegisterEnabled {
			user.Username = "lark_" + strconv.Itoa(model.GetMaxUserId()+1)
			if larkUser.Name != "" {
				user.DisplayName = larkUser.Name
			} else {
				user.DisplayName = "Lark User"
			}
			user.Role = model.RoleCommonUser
			user.Status = model.UserStatusEnabled

			if err := user.Insert(ctx, 0); err != nil {
				c.JSON(http.StatusOK, gin.H{
					"success": false,
					"message": err.Error(),
				})
				return
			}
		} else {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "管理员关闭了新用户注册",
			})
			return
		}
	}

	if user.Status != model.UserStatusEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "用户已被封禁",
			"success": false,
		})
		return
	}
	controller.SetupLogin(&user, c)
}

func LarkBind(c *gin.Context) {
	code := c.Query("code")
	larkUser, err := getLarkUserInfoByCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user := model.User{
		LarkId: larkUser.OpenID,
	}
	if model.IsLarkIdAlreadyTaken(user.LarkId) {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "该飞书账户已被绑定",
		})
		return
	}
	session := sessions.Default(c)
	id := session.Get("id")
	// id := c.GetInt("id")  // critical bug!
	user.Id = id.(int)
	err = user.FillUserById()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user.LarkId = larkUser.OpenID
	err = user.Update(false)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "bind",
	})
	return
}


================================================
FILE: controller/auth/oidc.go
================================================
package auth

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"

	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/logger"
	"github.com/songquanpeng/one-api/controller"
	"github.com/songquanpeng/one-api/model"
)

type OidcResponse struct {
	AccessToken  string `json:"access_token"`
	IDToken      string `json:"id_token"`
	RefreshToken string `json:"refresh_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	Scope        string `json:"scope"`
}

type OidcUser struct {
	OpenID            string `json:"sub"`
	Email             string `json:"email"`
	Name              string `json:"name"`
	PreferredUsername string `json:"preferred_username"`
	Picture           string `json:"picture"`
}

func getOidcUserInfoByCode(code string) (*OidcUser, error) {
	if code == "" {
		return nil, errors.New("无效的参数")
	}
	values := map[string]string{
		"client_id":     config.OidcClientId,
		"client_secret": config.OidcClientSecret,
		"code":          code,
		"grant_type":    "authorization_code",
		"redirect_uri":  fmt.Sprintf("%s/oauth/oidc", config.ServerAddress),
	}
	jsonData, err := json.Marshal(values)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", config.OidcTokenEndpoint, bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	client := http.Client{
		Timeout: 5 * time.Second,
	}
	res, err := client.Do(req)
	if err != nil {
		logger.SysLog(err.Error())
		return nil, errors.New("无法连接至 OIDC 服务器,请稍后重试!")
	}
	defer res.Body.Close()
	var oidcResponse OidcResponse
	err = json.NewDecoder(res.Body).Decode(&oidcResponse)
	if err != nil {
		return nil, err
	}
	req, err = http.NewRequest("GET", config.OidcUserinfoEndpoint, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+oidcResponse.AccessToken)
	res2, err := client.Do(req)
	if err != nil {
		logger.SysLog(err.Error())
		return nil, errors.New("无法连接至 OIDC 服务器,请稍后重试!")
	}
	var oidcUser OidcUser
	err = json.NewDecoder(res2.Body).Decode(&oidcUser)
	if err != nil {
		return nil, err
	}
	return &oidcUser, nil
}

func OidcAuth(c *gin.Context) {
	ctx := c.Request.Context()
	session := sessions.Default(c)
	state := c.Query("state")
	if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
		c.JSON(http.StatusForbidden, gin.H{
			"success": false,
			"message": "state is empty or not same",
		})
		return
	}
	username := session.Get("username")
	if username != nil {
		OidcBind(c)
		return
	}
	if !config.OidcEnabled {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "管理员未开启通过 OIDC 登录以及注册",
		})
		return
	}
	code := c.Query("code")
	oidcUser, err := getOidcUserInfoByCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user := model.User{
		OidcId: oidcUser.OpenID,
	}
	if model.IsOidcIdAlreadyTaken(user.OidcId) {
		err := user.FillUserByOidcId()
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": err.Error(),
			})
			return
		}
	} else {
		if config.RegisterEnabled {
			user.Email = oidcUser.Email
			if oidcUser.PreferredUsername != "" {
				user.Username = oidcUser.PreferredUsername
			} else {
				user.Username = "oidc_" + strconv.Itoa(model.GetMaxUserId()+1)
			}
			if oidcUser.Name != "" {
				user.DisplayName = oidcUser.Name
			} else {
				user.DisplayName = "OIDC User"
			}
			err := user.Insert(ctx, 0)
			if err != nil {
				c.JSON(http.StatusOK, gin.H{
					"success": false,
					"message": err.Error(),
				})
				return
			}
		} else {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "管理员关闭了新用户注册",
			})
			return
		}
	}

	if user.Status != model.UserStatusEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "用户已被封禁",
			"success": false,
		})
		return
	}
	controller.SetupLogin(&user, c)
}

func OidcBind(c *gin.Context) {
	if !config.OidcEnabled {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "管理员未开启通过 OIDC 登录以及注册",
		})
		return
	}
	code := c.Query("code")
	oidcUser, err := getOidcUserInfoByCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user := model.User{
		OidcId: oidcUser.OpenID,
	}
	if model.IsOidcIdAlreadyTaken(user.OidcId) {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "该 OIDC 账户已被绑定",
		})
		return
	}
	session := sessions.Default(c)
	id := session.Get("id")
	// id := c.GetInt("id")  // critical bug!
	user.Id = id.(int)
	err = user.FillUserById()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user.OidcId = oidcUser.OpenID
	err = user.Update(false)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "bind",
	})
	return
}


================================================
FILE: controller/auth/wechat.go
================================================
package auth

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/controller"
	"github.com/songquanpeng/one-api/model"
)

type wechatLoginResponse struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
	Data    string `json:"data"`
}

func getWeChatIdByCode(code string) (string, error) {
	if code == "" {
		return "", errors.New("无效的参数")
	}
	req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/wechat/user?code=%s", config.WeChatServerAddress, code), nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", config.WeChatServerToken)
	client := http.Client{
		Timeout: 5 * time.Second,
	}
	httpResponse, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer httpResponse.Body.Close()
	var res wechatLoginResponse
	err = json.NewDecoder(httpResponse.Body).Decode(&res)
	if err != nil {
		return "", err
	}
	if !res.Success {
		return "", errors.New(res.Message)
	}
	if res.Data == "" {
		return "", errors.New("验证码错误或已过期")
	}
	return res.Data, nil
}

func WeChatAuth(c *gin.Context) {
	ctx := c.Request.Context()
	if !config.WeChatAuthEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "管理员未开启通过微信登录以及注册",
			"success": false,
		})
		return
	}
	code := c.Query("code")
	wechatId, err := getWeChatIdByCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"message": err.Error(),
			"success": false,
		})
		return
	}
	user := model.User{
		WeChatId: wechatId,
	}
	if model.IsWeChatIdAlreadyTaken(wechatId) {
		err := user.FillUserByWeChatId()
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": err.Error(),
			})
			return
		}
	} else {
		if config.RegisterEnabled {
			user.Username = "wechat_" + strconv.Itoa(model.GetMaxUserId()+1)
			user.DisplayName = "WeChat User"
			user.Role = model.RoleCommonUser
			user.Status = model.UserStatusEnabled

			if err := user.Insert(ctx, 0); err != nil {
				c.JSON(http.StatusOK, gin.H{
					"success": false,
					"message": err.Error(),
				})
				return
			}
		} else {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "管理员关闭了新用户注册",
			})
			return
		}
	}

	if user.Status != model.UserStatusEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "用户已被封禁",
			"success": false,
		})
		return
	}
	controller.SetupLogin(&user, c)
}

func WeChatBind(c *gin.Context) {
	if !config.WeChatAuthEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "管理员未开启通过微信登录以及注册",
			"success": false,
		})
		return
	}
	code := c.Query("code")
	wechatId, err := getWeChatIdByCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"message": err.Error(),
			"success": false,
		})
		return
	}
	if model.IsWeChatIdAlreadyTaken(wechatId) {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "该微信账号已被绑定",
		})
		return
	}
	id := c.GetInt(ctxkey.Id)
	user := model.User{
		Id: id,
	}
	err = user.FillUserById()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	user.WeChatId = wechatId
	err = user.Update(false)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}


================================================
FILE: controller/billing.go
================================================
package controller

import (
	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/model"
	relaymodel "github.com/songquanpeng/one-api/relay/model"
)

func GetSubscription(c *gin.Context) {
	var remainQuota int64
	var usedQuota int64
	var err error
	var token *model.Token
	var expiredTime int64
	if config.DisplayTokenStatEnabled {
		tokenId := c.GetInt(ctxkey.TokenId)
		token, err = model.GetTokenById(tokenId)
		if err == nil {
			expiredTime = token.ExpiredTime
			remainQuota = token.RemainQuota
			usedQuota = token.UsedQuota
		}
	} else {
		userId := c.GetInt(ctxkey.Id)
		remainQuota, err = model.GetUserQuota(userId)
		if err != nil {
			usedQuota, err = model.GetUserUsedQuota(userId)
		}
	}
	if expiredTime <= 0 {
		expiredTime = 0
	}
	if err != nil {
		Error := relaymodel.Error{
			Message: err.Error(),
			Type:    "upstream_error",
		}
		c.JSON(200, gin.H{
			"error": Error,
		})
		return
	}
	quota := remainQuota + usedQuota
	amount := float64(quota)
	if config.DisplayInCurrencyEnabled {
		amount /= config.QuotaPerUnit
	}
	if token != nil && token.UnlimitedQuota {
		amount = 100000000
	}
	subscription := OpenAISubscriptionResponse{
		Object:             "billing_subscription",
		HasPaymentMethod:   true,
		SoftLimitUSD:       amount,
		HardLimitUSD:       amount,
		SystemHardLimitUSD: amount,
		AccessUntil:        expiredTime,
	}
	c.JSON(200, subscription)
	return
}

func GetUsage(c *gin.Context) {
	var quota int64
	var err error
	var token *model.Token
	if config.DisplayTokenStatEnabled {
		tokenId := c.GetInt(ctxkey.TokenId)
		token, err = model.GetTokenById(tokenId)
		quota = token.UsedQuota
	} else {
		userId := c.GetInt(ctxkey.Id)
		quota, err = model.GetUserUsedQuota(userId)
	}
	if err != nil {
		Error := relaymodel.Error{
			Message: err.Error(),
			Type:    "one_api_error",
		}
		c.JSON(200, gin.H{
			"error": Error,
		})
		return
	}
	amount := float64(quota)
	if config.DisplayInCurrencyEnabled {
		amount /= config.QuotaPerUnit
	}
	usage := OpenAIUsageResponse{
		Object:     "list",
		TotalUsage: amount * 100,
	}
	c.JSON(200, usage)
	return
}


================================================
FILE: controller/channel-billing.go
================================================
package controller

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"time"

	"github.com/songquanpeng/one-api/common/client"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/logger"
	"github.com/songquanpeng/one-api/model"
	"github.com/songquanpeng/one-api/monitor"
	"github.com/songquanpeng/one-api/relay/channeltype"

	"github.com/gin-gonic/gin"
)

// https://github.com/songquanpeng/one-api/issues/79

type OpenAISubscriptionResponse struct {
	Object             string  `json:"object"`
	HasPaymentMethod   bool    `json:"has_payment_method"`
	SoftLimitUSD       float64 `json:"soft_limit_usd"`
	HardLimitUSD       float64 `json:"hard_limit_usd"`
	SystemHardLimitUSD float64 `json:"system_hard_limit_usd"`
	AccessUntil        int64   `json:"access_until"`
}

type OpenAIUsageDailyCost struct {
	Timestamp float64 `json:"timestamp"`
	LineItems []struct {
		Name string  `json:"name"`
		Cost float64 `json:"cost"`
	}
}

type OpenAICreditGrants struct {
	Object         string  `json:"object"`
	TotalGranted   float64 `json:"total_granted"`
	TotalUsed      float64 `json:"total_used"`
	TotalAvailable float64 `json:"total_available"`
}

type OpenAIUsageResponse struct {
	Object string `json:"object"`
	//DailyCosts []OpenAIUsageDailyCost `json:"daily_costs"`
	TotalUsage float64 `json:"total_usage"` // unit: 0.01 dollar
}

type OpenAISBUsageResponse struct {
	Msg  string `json:"msg"`
	Data *struct {
		Credit string `json:"credit"`
	} `json:"data"`
}

type AIProxyUserOverviewResponse struct {
	Success   bool   `json:"success"`
	Message   string `json:"message"`
	ErrorCode int    `json:"error_code"`
	Data      struct {
		TotalPoints float64 `json:"totalPoints"`
	} `json:"data"`
}

type API2GPTUsageResponse struct {
	Object         string  `json:"object"`
	TotalGranted   float64 `json:"total_granted"`
	TotalUsed      float64 `json:"total_used"`
	TotalRemaining float64 `json:"total_remaining"`
}

type APGC2DGPTUsageResponse struct {
	//Grants         interface{} `json:"grants"`
	Object         string  `json:"object"`
	TotalAvailable float64 `json:"total_available"`
	TotalGranted   float64 `json:"total_granted"`
	TotalUsed      float64 `json:"total_used"`
}

type SiliconFlowUsageResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Status  bool   `json:"status"`
	Data    struct {
		ID            string `json:"id"`
		Name          string `json:"name"`
		Image         string `json:"image"`
		Email         string `json:"email"`
		IsAdmin       bool   `json:"isAdmin"`
		Balance       string `json:"balance"`
		Status        string `json:"status"`
		Introduction  string `json:"introduction"`
		Role          string `json:"role"`
		ChargeBalance string `json:"chargeBalance"`
		TotalBalance  string `json:"totalBalance"`
		Category      string `json:"category"`
	} `json:"data"`
}

type DeepSeekUsageResponse struct {
	IsAvailable  bool `json:"is_available"`
	BalanceInfos []struct {
		Currency        string `json:"currency"`
		TotalBalance    string `json:"total_balance"`
		GrantedBalance  string `json:"granted_balance"`
		ToppedUpBalance string `json:"topped_up_balance"`
	} `json:"balance_infos"`
}

type OpenRouterResponse struct {
	Data struct {
		TotalCredits float64 `json:"total_credits"`
		TotalUsage   float64 `json:"total_usage"`
	} `json:"data"`
}

// GetAuthHeader get auth header
func GetAuthHeader(token string) http.Header {
	h := http.Header{}
	h.Add("Authorization", fmt.Sprintf("Bearer %s", token))
	return h
}

func GetResponseBody(method, url string, channel *model.Channel, headers http.Header) ([]byte, error) {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	for k := range headers {
		req.Header.Add(k, headers.Get(k))
	}
	res, err := client.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}
	if res.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("status code: %d", res.StatusCode)
	}
	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	err = res.Body.Close()
	if err != nil {
		return nil, err
	}
	return body, nil
}

func updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {
	url := fmt.Sprintf("%s/dashboard/billing/credit_grants", channel.GetBaseURL())
	body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))

	if err != nil {
		return 0, err
	}
	response := OpenAICreditGrants{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}
	channel.UpdateBalance(response.TotalAvailable)
	return response.TotalAvailable, nil
}

func updateChannelOpenAISBBalance(channel *model.Channel) (float64, error) {
	url := fmt.Sprintf("https://api.openai-sb.com/sb-api/user/status?api_key=%s", channel.Key)
	body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
	if err != nil {
		return 0, err
	}
	response := OpenAISBUsageResponse{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}
	if response.Data == nil {
		return 0, errors.New(response.Msg)
	}
	balance, err := strconv.ParseFloat(response.Data.Credit, 64)
	if err != nil {
		return 0, err
	}
	channel.UpdateBalance(balance)
	return balance, nil
}

func updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {
	url := "https://aiproxy.io/api/report/getUserOverview"
	headers := http.Header{}
	headers.Add("Api-Key", channel.Key)
	body, err := GetResponseBody("GET", url, channel, headers)
	if err != nil {
		return 0, err
	}
	response := AIProxyUserOverviewResponse{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}
	if !response.Success {
		return 0, fmt.Errorf("code: %d, message: %s", response.ErrorCode, response.Message)
	}
	channel.UpdateBalance(response.Data.TotalPoints)
	return response.Data.TotalPoints, nil
}

func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
	url := "https://api.api2gpt.com/dashboard/billing/credit_grants"
	body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))

	if err != nil {
		return 0, err
	}
	response := API2GPTUsageResponse{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}
	channel.UpdateBalance(response.TotalRemaining)
	return response.TotalRemaining, nil
}

func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
	url := "https://api.aigc2d.com/dashboard/billing/credit_grants"
	body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
	if err != nil {
		return 0, err
	}
	response := APGC2DGPTUsageResponse{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}
	channel.UpdateBalance(response.TotalAvailable)
	return response.TotalAvailable, nil
}

func updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {
	url := "https://api.siliconflow.cn/v1/user/info"
	body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
	if err != nil {
		return 0, err
	}
	response := SiliconFlowUsageResponse{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}
	if response.Code != 20000 {
		return 0, fmt.Errorf("code: %d, message: %s", response.Code, response.Message)
	}
	balance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)
	if err != nil {
		return 0, err
	}
	channel.UpdateBalance(balance)
	return balance, nil
}

func updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {
	url := "https://api.deepseek.com/user/balance"
	body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
	if err != nil {
		return 0, err
	}
	response := DeepSeekUsageResponse{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}
	index := -1
	for i, balanceInfo := range response.BalanceInfos {
		if balanceInfo.Currency == "CNY" {
			index = i
			break
		}
	}
	if index == -1 {
		return 0, errors.New("currency CNY not found")
	}
	balance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)
	if err != nil {
		return 0, err
	}
	channel.UpdateBalance(balance)
	return balance, nil
}

func updateChannelOpenRouterBalance(channel *model.Channel) (float64, error) {
	url := "https://openrouter.ai/api/v1/credits"
	body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
	if err != nil {
		return 0, err
	}
	response := OpenRouterResponse{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}
	balance := response.Data.TotalCredits - response.Data.TotalUsage
	channel.UpdateBalance(balance)
	return balance, nil
}

func updateChannelBalance(channel *model.Channel) (float64, error) {
	baseURL := channeltype.ChannelBaseURLs[channel.Type]
	if channel.GetBaseURL() == "" {
		channel.BaseURL = &baseURL
	}
	switch channel.Type {
	case channeltype.OpenAI:
		if channel.GetBaseURL() != "" {
			baseURL = channel.GetBaseURL()
		}
	case channeltype.Azure:
		return 0, errors.New("尚未实现")
	case channeltype.Custom:
		baseURL = channel.GetBaseURL()
	case channeltype.CloseAI:
		return updateChannelCloseAIBalance(channel)
	case channeltype.OpenAISB:
		return updateChannelOpenAISBBalance(channel)
	case channeltype.AIProxy:
		return updateChannelAIProxyBalance(channel)
	case channeltype.API2GPT:
		return updateChannelAPI2GPTBalance(channel)
	case channeltype.AIGC2D:
		return updateChannelAIGC2DBalance(channel)
	case channeltype.SiliconFlow:
		return updateChannelSiliconFlowBalance(channel)
	case channeltype.DeepSeek:
		return updateChannelDeepSeekBalance(channel)
	case channeltype.OpenRouter:
		return updateChannelOpenRouterBalance(channel)
	default:
		return 0, errors.New("尚未实现")
	}
	url := fmt.Sprintf("%s/v1/dashboard/billing/subscription", baseURL)

	body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
	if err != nil {
		return 0, err
	}
	subscription := OpenAISubscriptionResponse{}
	err = json.Unmarshal(body, &subscription)
	if err != nil {
		return 0, err
	}
	now := time.Now()
	startDate := fmt.Sprintf("%s-01", now.Format("2006-01"))
	endDate := now.Format("2006-01-02")
	if !subscription.HasPaymentMethod {
		startDate = now.AddDate(0, 0, -100).Format("2006-01-02")
	}
	url = fmt.Sprintf("%s/v1/dashboard/billing/usage?start_date=%s&end_date=%s", baseURL, startDate, endDate)
	body, err = GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
	if err != nil {
		return 0, err
	}
	usage := OpenAIUsageResponse{}
	err = json.Unmarshal(body, &usage)
	if err != nil {
		return 0, err
	}
	balance := subscription.HardLimitUSD - usage.TotalUsage/100
	channel.UpdateBalance(balance)
	return balance, nil
}

func UpdateChannelBalance(c *gin.Context) {
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	channel, err := model.GetChannelById(id, true)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	balance, err := updateChannelBalance(channel)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"balance": balance,
	})
	return
}

func updateAllChannelsBalance() error {
	channels, err := model.GetAllChannels(0, 0, "all")
	if err != nil {
		return err
	}
	for _, channel := range channels {
		if channel.Status != model.ChannelStatusEnabled {
			continue
		}
		// TODO: support Azure
		if channel.Type != channeltype.OpenAI && channel.Type != channeltype.Custom {
			continue
		}
		balance, err := updateChannelBalance(channel)
		if err != nil {
			continue
		} else {
			// err is nil & balance <= 0 means quota is used up
			if balance <= 0 {
				monitor.DisableChannel(channel.Id, channel.Name, "余额不足")
			}
		}
		time.Sleep(config.RequestInterval)
	}
	return nil
}

func UpdateAllChannelsBalance(c *gin.Context) {
	//err := updateAllChannelsBalance()
	//if err != nil {
	//	c.JSON(http.StatusOK, gin.H{
	//		"success": false,
	//		"message": err.Error(),
	//	})
	//	return
	//}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}

func AutomaticallyUpdateChannels(frequency int) {
	for {
		time.Sleep(time.Duration(frequency) * time.Minute)
		logger.SysLog("updating all channels")
		_ = updateAllChannelsBalance()
		logger.SysLog("channels update done")
	}
}


================================================
FILE: controller/channel-test.go
================================================
package controller

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/common/helper"
	"github.com/songquanpeng/one-api/common/logger"
	"github.com/songquanpeng/one-api/common/message"
	"github.com/songquanpeng/one-api/middleware"
	"github.com/songquanpeng/one-api/model"
	"github.com/songquanpeng/one-api/monitor"
	"github.com/songquanpeng/one-api/relay"
	"github.com/songquanpeng/one-api/relay/adaptor/openai"
	"github.com/songquanpeng/one-api/relay/channeltype"
	"github.com/songquanpeng/one-api/relay/controller"
	"github.com/songquanpeng/one-api/relay/meta"
	relaymodel "github.com/songquanpeng/one-api/relay/model"
	"github.com/songquanpeng/one-api/relay/relaymode"
)

func buildTestRequest(model string) *relaymodel.GeneralOpenAIRequest {
	if model == "" {
		model = "gpt-3.5-turbo"
	}
	testRequest := &relaymodel.GeneralOpenAIRequest{
		Model: model,
	}
	testMessage := relaymodel.Message{
		Role:    "user",
		Content: config.TestPrompt,
	}
	testRequest.Messages = append(testRequest.Messages, testMessage)
	return testRequest
}

func parseTestResponse(resp string) (*openai.TextResponse, string, error) {
	var response openai.TextResponse
	err := json.Unmarshal([]byte(resp), &response)
	if err != nil {
		return nil, "", err
	}
	if len(response.Choices) == 0 {
		return nil, "", errors.New("response has no choices")
	}
	stringContent, ok := response.Choices[0].Content.(string)
	if !ok {
		return nil, "", errors.New("response content is not string")
	}
	return &response, stringContent, nil
}

func testChannel(ctx context.Context, channel *model.Channel, request *relaymodel.GeneralOpenAIRequest) (responseMessage string, err error, openaiErr *relaymodel.Error) {
	startTime := time.Now()
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = &http.Request{
		Method: "POST",
		URL:    &url.URL{Path: "/v1/chat/completions"},
		Body:   nil,
		Header: make(http.Header),
	}
	c.Request.Header.Set("Authorization", "Bearer "+channel.Key)
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(ctxkey.Channel, channel.Type)
	c.Set(ctxkey.BaseURL, channel.GetBaseURL())
	cfg, _ := channel.LoadConfig()
	c.Set(ctxkey.Config, cfg)
	middleware.SetupContextForSelectedChannel(c, channel, "")
	meta := meta.GetByContext(c)
	apiType := channeltype.ToAPIType(channel.Type)
	adaptor := relay.GetAdaptor(apiType)
	if adaptor == nil {
		return "", fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), nil
	}
	adaptor.Init(meta)
	modelName := request.Model
	modelMap := channel.GetModelMapping()
	if modelName == "" || !strings.Contains(channel.Models, modelName) {
		modelNames := strings.Split(channel.Models, ",")
		if len(modelNames) > 0 {
			modelName = modelNames[0]
		}
	}
	if modelMap != nil && modelMap[modelName] != "" {
		modelName = modelMap[modelName]
	}
	meta.OriginModelName, meta.ActualModelName = request.Model, modelName
	request.Model = modelName
	convertedRequest, err := adaptor.ConvertRequest(c, relaymode.ChatCompletions, request)
	if err != nil {
		return "", err, nil
	}
	jsonData, err := json.Marshal(convertedRequest)
	if err != nil {
		return "", err, nil
	}
	defer func() {
		logContent := fmt.Sprintf("渠道 %s 测试成功,响应:%s", channel.Name, responseMessage)
		if err != nil || openaiErr != nil {
			errorMessage := ""
			if err != nil {
				errorMessage = err.Error()
			} else {
				errorMessage = openaiErr.Message
			}
			logContent = fmt.Sprintf("渠道 %s 测试失败,错误:%s", channel.Name, errorMessage)
		}
		go model.RecordTestLog(ctx, &model.Log{
			ChannelId:   channel.Id,
			ModelName:   modelName,
			Content:     logContent,
			ElapsedTime: helper.CalcElapsedTime(startTime),
		})
	}()
	logger.SysLog(string(jsonData))
	requestBody := bytes.NewBuffer(jsonData)
	c.Request.Body = io.NopCloser(requestBody)
	resp, err := adaptor.DoRequest(c, meta, requestBody)
	if err != nil {
		return "", err, nil
	}
	if resp != nil && resp.StatusCode != http.StatusOK {
		err := controller.RelayErrorHandler(resp)
		errorMessage := err.Error.Message
		if errorMessage != "" {
			errorMessage = ", error message: " + errorMessage
		}
		return "", fmt.Errorf("http status code: %d%s", resp.StatusCode, errorMessage), &err.Error
	}
	usage, respErr := adaptor.DoResponse(c, resp, meta)
	if respErr != nil {
		return "", fmt.Errorf("%s", respErr.Error.Message), &respErr.Error
	}
	if usage == nil {
		return "", errors.New("usage is nil"), nil
	}
	rawResponse := w.Body.String()
	_, responseMessage, err = parseTestResponse(rawResponse)
	if err != nil {
		logger.SysError(fmt.Sprintf("failed to parse error: %s, \nresponse: %s", err.Error(), rawResponse))
		return "", err, nil
	}
	result := w.Result()
	// print result.Body
	respBody, err := io.ReadAll(result.Body)
	if err != nil {
		return "", err, nil
	}
	logger.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))
	return responseMessage, nil, nil
}

func TestChannel(c *gin.Context) {
	ctx := c.Request.Context()
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	channel, err := model.GetChannelById(id, true)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	modelName := c.Query("model")
	testRequest := buildTestRequest(modelName)
	tik := time.Now()
	responseMessage, err, _ := testChannel(ctx, channel, testRequest)
	tok := time.Now()
	milliseconds := tok.Sub(tik).Milliseconds()
	if err != nil {
		milliseconds = 0
	}
	go channel.UpdateResponseTime(milliseconds)
	consumedTime := float64(milliseconds) / 1000.0
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success":   false,
			"message":   err.Error(),
			"time":      consumedTime,
			"modelName": modelName,
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success":   true,
		"message":   responseMessage,
		"time":      consumedTime,
		"modelName": modelName,
	})
	return
}

var testAllChannelsLock sync.Mutex
var testAllChannelsRunning bool = false

func testChannels(ctx context.Context, notify bool, scope string) error {
	if config.RootUserEmail == "" {
		config.RootUserEmail = model.GetRootUserEmail()
	}
	testAllChannelsLock.Lock()
	if testAllChannelsRunning {
		testAllChannelsLock.Unlock()
		return errors.New("测试已在运行中")
	}
	testAllChannelsRunning = true
	testAllChannelsLock.Unlock()
	channels, err := model.GetAllChannels(0, 0, scope)
	if err != nil {
		return err
	}
	var disableThreshold = int64(config.ChannelDisableThreshold * 1000)
	if disableThreshold == 0 {
		disableThreshold = 10000000 // a impossible value
	}
	go func() {
		for _, channel := range channels {
			isChannelEnabled := channel.Status == model.ChannelStatusEnabled
			tik := time.Now()
			testRequest := buildTestRequest("")
			_, err, openaiErr := testChannel(ctx, channel, testRequest)
			tok := time.Now()
			milliseconds := tok.Sub(tik).Milliseconds()
			if isChannelEnabled && milliseconds > disableThreshold {
				err = fmt.Errorf("响应时间 %.2fs 超过阈值 %.2fs", float64(milliseconds)/1000.0, float64(disableThreshold)/1000.0)
				if config.AutomaticDisableChannelEnabled {
					monitor.DisableChannel(channel.Id, channel.Name, err.Error())
				} else {
					_ = message.Notify(message.ByAll, fmt.Sprintf("渠道 %s (%d)测试超时", channel.Name, channel.Id), "", err.Error())
				}
			}
			if isChannelEnabled && monitor.ShouldDisableChannel(openaiErr, -1) {
				monitor.DisableChannel(channel.Id, channel.Name, err.Error())
			}
			if !isChannelEnabled && monitor.ShouldEnableChannel(err, openaiErr) {
				monitor.EnableChannel(channel.Id, channel.Name)
			}
			channel.UpdateResponseTime(milliseconds)
			time.Sleep(config.RequestInterval)
		}
		testAllChannelsLock.Lock()
		testAllChannelsRunning = false
		testAllChannelsLock.Unlock()
		if notify {
			err := message.Notify(message.ByAll, "渠道测试完成", "", "渠道测试完成,如果没有收到禁用通知,说明所有渠道都正常")
			if err != nil {
				logger.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
			}
		}
	}()
	return nil
}

func TestChannels(c *gin.Context) {
	ctx := c.Request.Context()
	scope := c.Query("scope")
	if scope == "" {
		scope = "all"
	}
	err := testChannels(ctx, true, scope)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}

func AutomaticallyTestChannels(frequency int) {
	ctx := context.Background()
	for {
		time.Sleep(time.Duration(frequency) * time.Minute)
		logger.SysLog("testing all channels")
		_ = testChannels(ctx, false, "all")
		logger.SysLog("channel test finished")
	}
}


================================================
FILE: controller/channel.go
================================================
package controller

import (
	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/helper"
	"github.com/songquanpeng/one-api/model"
	"net/http"
	"strconv"
	"strings"
)

func GetAllChannels(c *gin.Context) {
	p, _ := strconv.Atoi(c.Query("p"))
	if p < 0 {
		p = 0
	}
	channels, err := model.GetAllChannels(p*config.ItemsPerPage, config.ItemsPerPage, "limited")
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    channels,
	})
	return
}

func SearchChannels(c *gin.Context) {
	keyword := c.Query("keyword")
	channels, err := model.SearchChannels(keyword)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    channels,
	})
	return
}

func GetChannel(c *gin.Context) {
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	channel, err := model.GetChannelById(id, false)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    channel,
	})
	return
}

func AddChannel(c *gin.Context) {
	channel := model.Channel{}
	err := c.ShouldBindJSON(&channel)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	channel.CreatedTime = helper.GetTimestamp()
	keys := strings.Split(channel.Key, "\n")
	channels := make([]model.Channel, 0, len(keys))
	for _, key := range keys {
		if key == "" {
			continue
		}
		localChannel := channel
		localChannel.Key = key
		channels = append(channels, localChannel)
	}
	err = model.BatchInsertChannels(channels)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}

func DeleteChannel(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	channel := model.Channel{Id: id}
	err := channel.Delete()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}

func DeleteDisabledChannel(c *gin.Context) {
	rows, err := model.DeleteDisabledChannel()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    rows,
	})
	return
}

func UpdateChannel(c *gin.Context) {
	channel := model.Channel{}
	err := c.ShouldBindJSON(&channel)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	err = channel.Update()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    channel,
	})
	return
}


================================================
FILE: controller/group.go
================================================
package controller

import (
	"github.com/gin-gonic/gin"
	billingratio "github.com/songquanpeng/one-api/relay/billing/ratio"
	"net/http"
)

func GetGroups(c *gin.Context) {
	groupNames := make([]string, 0)
	for groupName := range billingratio.GroupRatio {
		groupNames = append(groupNames, groupName)
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    groupNames,
	})
}


================================================
FILE: controller/log.go
================================================
package controller

import (
	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/model"
	"net/http"
	"strconv"
)

func GetAllLogs(c *gin.Context) {
	p, _ := strconv.Atoi(c.Query("p"))
	if p < 0 {
		p = 0
	}
	logType, _ := strconv.Atoi(c.Query("type"))
	startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
	endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
	username := c.Query("username")
	tokenName := c.Query("token_name")
	modelName := c.Query("model_name")
	channel, _ := strconv.Atoi(c.Query("channel"))
	logs, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, tokenName, p*config.ItemsPerPage, config.ItemsPerPage, channel)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    logs,
	})
	return
}

func GetUserLogs(c *gin.Context) {
	p, _ := strconv.Atoi(c.Query("p"))
	if p < 0 {
		p = 0
	}
	userId := c.GetInt(ctxkey.Id)
	logType, _ := strconv.Atoi(c.Query("type"))
	startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
	endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
	tokenName := c.Query("token_name")
	modelName := c.Query("model_name")
	logs, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, p*config.ItemsPerPage, config.ItemsPerPage)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    logs,
	})
	return
}

func SearchAllLogs(c *gin.Context) {
	keyword := c.Query("keyword")
	logs, err := model.SearchAllLogs(keyword)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    logs,
	})
	return
}

func SearchUserLogs(c *gin.Context) {
	keyword := c.Query("keyword")
	userId := c.GetInt(ctxkey.Id)
	logs, err := model.SearchUserLogs(userId, keyword)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    logs,
	})
	return
}

func GetLogsStat(c *gin.Context) {
	logType, _ := strconv.Atoi(c.Query("type"))
	startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
	endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
	tokenName := c.Query("token_name")
	username := c.Query("username")
	modelName := c.Query("model_name")
	channel, _ := strconv.Atoi(c.Query("channel"))
	quotaNum := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel)
	//tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, "")
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data": gin.H{
			"quota": quotaNum,
			//"token": tokenNum,
		},
	})
	return
}

func GetLogsSelfStat(c *gin.Context) {
	username := c.GetString(ctxkey.Username)
	logType, _ := strconv.Atoi(c.Query("type"))
	startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
	endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
	tokenName := c.Query("token_name")
	modelName := c.Query("model_name")
	channel, _ := strconv.Atoi(c.Query("channel"))
	quotaNum := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel)
	//tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, tokenName)
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data": gin.H{
			"quota": quotaNum,
			//"token": tokenNum,
		},
	})
	return
}

func DeleteHistoryLogs(c *gin.Context) {
	targetTimestamp, _ := strconv.ParseInt(c.Query("target_timestamp"), 10, 64)
	if targetTimestamp == 0 {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "target timestamp is required",
		})
		return
	}
	count, err := model.DeleteOldLog(targetTimestamp)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    count,
	})
	return
}


================================================
FILE: controller/misc.go
================================================
package controller

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/songquanpeng/one-api/common"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/i18n"
	"github.com/songquanpeng/one-api/common/message"
	"github.com/songquanpeng/one-api/model"

	"github.com/gin-gonic/gin"
)

func GetStatus(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data": gin.H{
			"version":                     common.Version,
			"start_time":                  common.StartTime,
			"email_verification":          config.EmailVerificationEnabled,
			"github_oauth":                config.GitHubOAuthEnabled,
			"github_client_id":            config.GitHubClientId,
			"lark_client_id":              config.LarkClientId,
			"system_name":                 config.SystemName,
			"logo":                        config.Logo,
			"footer_html":                 config.Footer,
			"wechat_qrcode":               config.WeChatAccountQRCodeImageURL,
			"wechat_login":                config.WeChatAuthEnabled,
			"server_address":              config.ServerAddress,
			"turnstile_check":             config.TurnstileCheckEnabled,
			"turnstile_site_key":          config.TurnstileSiteKey,
			"top_up_link":                 config.TopUpLink,
			"chat_link":                   config.ChatLink,
			"quota_per_unit":              config.QuotaPerUnit,
			"display_in_currency":         config.DisplayInCurrencyEnabled,
			"oidc":                        config.OidcEnabled,
			"oidc_client_id":              config.OidcClientId,
			"oidc_well_known":             config.OidcWellKnown,
			"oidc_authorization_endpoint": config.OidcAuthorizationEndpoint,
			"oidc_token_endpoint":         config.OidcTokenEndpoint,
			"oidc_userinfo_endpoint":      config.OidcUserinfoEndpoint,
		},
	})
	return
}

func GetNotice(c *gin.Context) {
	config.OptionMapRWMutex.RLock()
	defer config.OptionMapRWMutex.RUnlock()
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    config.OptionMap["Notice"],
	})
	return
}

func GetAbout(c *gin.Context) {
	config.OptionMapRWMutex.RLock()
	defer config.OptionMapRWMutex.RUnlock()
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    config.OptionMap["About"],
	})
	return
}

func GetHomePageContent(c *gin.Context) {
	config.OptionMapRWMutex.RLock()
	defer config.OptionMapRWMutex.RUnlock()
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    config.OptionMap["HomePageContent"],
	})
	return
}

func SendEmailVerification(c *gin.Context) {
	email := c.Query("email")
	if err := common.Validate.Var(email, "required,email"); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": i18n.Translate(c, "invalid_parameter"),
		})
		return
	}
	if config.EmailDomainRestrictionEnabled {
		allowed := false
		for _, domain := range config.EmailDomainWhitelist {
			if strings.HasSuffix(email, "@"+domain) {
				allowed = true
				break
			}
		}
		if !allowed {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "管理员启用了邮箱域名白名单,您的邮箱地址的域名不在白名单中",
			})
			return
		}
	}
	if model.IsEmailAlreadyTaken(email) {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "邮箱地址已被占用",
		})
		return
	}
	code := common.GenerateVerificationCode(6)
	common.RegisterVerificationCodeWithKey(email, code, common.EmailVerificationPurpose)
	subject := fmt.Sprintf("%s 邮箱验证邮件", config.SystemName)
	content := message.EmailTemplate(
		subject,
		fmt.Sprintf(`
			<p>您好!</p>
			<p>您正在进行 %s 邮箱验证。</p>
			<p>您的验证码为:</p>
			<p style="font-size: 24px; font-weight: bold; color: #333; background-color: #f8f8f8; padding: 10px; text-align: center; border-radius: 4px;">%s</p>
			<p style="color: #666;">验证码 %d 分钟内有效,如果不是本人操作,请忽略。</p>
		`, config.SystemName, code, common.VerificationValidMinutes),
	)
	err := message.SendEmail(subject, email, content)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}

func SendPasswordResetEmail(c *gin.Context) {
	email := c.Query("email")
	if err := common.Validate.Var(email, "required,email"); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": i18n.Translate(c, "invalid_parameter"),
		})
		return
	}
	if !model.IsEmailAlreadyTaken(email) {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "该邮箱地址未注册",
		})
		return
	}
	code := common.GenerateVerificationCode(0)
	common.RegisterVerificationCodeWithKey(email, code, common.PasswordResetPurpose)
	link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", config.ServerAddress, email, code)
	subject := fmt.Sprintf("%s 密码重置", config.SystemName)
	content := message.EmailTemplate(
		subject,
		fmt.Sprintf(`
			<p>您好!</p>
			<p>您正在进行 %s 密码重置。</p>
			<p>请点击下面的按钮进行密码重置:</p>
			<p style="text-align: center; margin: 30px 0;">
				<a href="%s" style="background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">重置密码</a>
			</p>
			<p style="color: #666;">如果按钮无法点击,请复制以下链接到浏览器中打开:</p>
			<p style="background-color: #f8f8f8; padding: 10px; border-radius: 4px; word-break: break-all;">%s</p>
			<p style="color: #666;">重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>
		`, config.SystemName, link, link, common.VerificationValidMinutes),
	)
	err := message.SendEmail(subject, email, content)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": fmt.Sprintf("%s%s", i18n.Translate(c, "send_email_failed"), err.Error()),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}

type PasswordResetRequest struct {
	Email string `json:"email"`
	Token string `json:"token"`
}

func ResetPassword(c *gin.Context) {
	var req PasswordResetRequest
	err := json.NewDecoder(c.Request.Body).Decode(&req)
	if req.Email == "" || req.Token == "" {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": i18n.Translate(c, "invalid_parameter"),
		})
		return
	}
	if !common.VerifyCodeWithKey(req.Email, req.Token, common.PasswordResetPurpose) {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "重置链接非法或已过期",
		})
		return
	}
	password := common.GenerateVerificationCode(12)
	err = model.ResetUserPasswordByEmail(req.Email, password)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	common.DeleteKey(req.Email, common.PasswordResetPurpose)
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    password,
	})
	return
}


================================================
FILE: controller/model.go
================================================
package controller

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/model"
	relay "github.com/songquanpeng/one-api/relay"
	"github.com/songquanpeng/one-api/relay/adaptor/openai"
	"github.com/songquanpeng/one-api/relay/apitype"
	"github.com/songquanpeng/one-api/relay/channeltype"
	"github.com/songquanpeng/one-api/relay/meta"
	relaymodel "github.com/songquanpeng/one-api/relay/model"
	"net/http"
	"strings"
)

// https://platform.openai.com/docs/api-reference/models/list

type OpenAIModelPermission struct {
	Id                 string  `json:"id"`
	Object             string  `json:"object"`
	Created            int     `json:"created"`
	AllowCreateEngine  bool    `json:"allow_create_engine"`
	AllowSampling      bool    `json:"allow_sampling"`
	AllowLogprobs      bool    `json:"allow_logprobs"`
	AllowSearchIndices bool    `json:"allow_search_indices"`
	AllowView          bool    `json:"allow_view"`
	AllowFineTuning    bool    `json:"allow_fine_tuning"`
	Organization       string  `json:"organization"`
	Group              *string `json:"group"`
	IsBlocking         bool    `json:"is_blocking"`
}

type OpenAIModels struct {
	Id         string                  `json:"id"`
	Object     string                  `json:"object"`
	Created    int                     `json:"created"`
	OwnedBy    string                  `json:"owned_by"`
	Permission []OpenAIModelPermission `json:"permission"`
	Root       string                  `json:"root"`
	Parent     *string                 `json:"parent"`
}

var models []OpenAIModels
var modelsMap map[string]OpenAIModels
var channelId2Models map[int][]string

func init() {
	var permission []OpenAIModelPermission
	permission = append(permission, OpenAIModelPermission{
		Id:                 "modelperm-LwHkVFn8AcMItP432fKKDIKJ",
		Object:             "model_permission",
		Created:            1626777600,
		AllowCreateEngine:  true,
		AllowSampling:      true,
		AllowLogprobs:      true,
		AllowSearchIndices: false,
		AllowView:          true,
		AllowFineTuning:    false,
		Organization:       "*",
		Group:              nil,
		IsBlocking:         false,
	})
	// https://platform.openai.com/docs/models/model-endpoint-compatibility
	for i := 0; i < apitype.Dummy; i++ {
		if i == apitype.AIProxyLibrary {
			continue
		}
		adaptor := relay.GetAdaptor(i)
		channelName := adaptor.GetChannelName()
		modelNames := adaptor.GetModelList()
		for _, modelName := range modelNames {
			models = append(models, OpenAIModels{
				Id:         modelName,
				Object:     "model",
				Created:    1626777600,
				OwnedBy:    channelName,
				Permission: permission,
				Root:       modelName,
				Parent:     nil,
			})
		}
	}
	for _, channelType := range openai.CompatibleChannels {
		if channelType == channeltype.Azure {
			continue
		}
		channelName, channelModelList := openai.GetCompatibleChannelMeta(channelType)
		for _, modelName := range channelModelList {
			models = append(models, OpenAIModels{
				Id:         modelName,
				Object:     "model",
				Created:    1626777600,
				OwnedBy:    channelName,
				Permission: permission,
				Root:       modelName,
				Parent:     nil,
			})
		}
	}
	modelsMap = make(map[string]OpenAIModels)
	for _, model := range models {
		modelsMap[model.Id] = model
	}
	channelId2Models = make(map[int][]string)
	for i := 1; i < channeltype.Dummy; i++ {
		adaptor := relay.GetAdaptor(channeltype.ToAPIType(i))
		meta := &meta.Meta{
			ChannelType: i,
		}
		adaptor.Init(meta)
		channelId2Models[i] = adaptor.GetModelList()
	}
}

func DashboardListModels(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    channelId2Models,
	})
}

func ListAllModels(c *gin.Context) {
	c.JSON(200, gin.H{
		"object": "list",
		"data":   models,
	})
}

func ListModels(c *gin.Context) {
	ctx := c.Request.Context()
	var availableModels []string
	if c.GetString(ctxkey.AvailableModels) != "" {
		availableModels = strings.Split(c.GetString(ctxkey.AvailableModels), ",")
	} else {
		userId := c.GetInt(ctxkey.Id)
		userGroup, _ := model.CacheGetUserGroup(userId)
		availableModels, _ = model.CacheGetGroupModels(ctx, userGroup)
	}
	modelSet := make(map[string]bool)
	for _, availableModel := range availableModels {
		modelSet[availableModel] = true
	}
	availableOpenAIModels := make([]OpenAIModels, 0)
	for _, model := range models {
		if _, ok := modelSet[model.Id]; ok {
			modelSet[model.Id] = false
			availableOpenAIModels = append(availableOpenAIModels, model)
		}
	}
	for modelName, ok := range modelSet {
		if ok {
			availableOpenAIModels = append(availableOpenAIModels, OpenAIModels{
				Id:      modelName,
				Object:  "model",
				Created: 1626777600,
				OwnedBy: "custom",
				Root:    modelName,
				Parent:  nil,
			})
		}
	}
	c.JSON(200, gin.H{
		"object": "list",
		"data":   availableOpenAIModels,
	})
}

func RetrieveModel(c *gin.Context) {
	modelId := c.Param("model")
	if model, ok := modelsMap[modelId]; ok {
		c.JSON(200, model)
	} else {
		Error := relaymodel.Error{
			Message: fmt.Sprintf("The model '%s' does not exist", modelId),
			Type:    "invalid_request_error",
			Param:   "model",
			Code:    "model_not_found",
		}
		c.JSON(200, gin.H{
			"error": Error,
		})
	}
}

func GetUserAvailableModels(c *gin.Context) {
	ctx := c.Request.Context()
	id := c.GetInt(ctxkey.Id)
	userGroup, err := model.CacheGetUserGroup(id)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	models, err := model.CacheGetGroupModels(ctx, userGroup)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    models,
	})
	return
}


================================================
FILE: controller/option.go
================================================
package controller

import (
	"encoding/json"
	"net/http"
	"strings"

	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/helper"
	"github.com/songquanpeng/one-api/common/i18n"
	"github.com/songquanpeng/one-api/model"

	"github.com/gin-gonic/gin"
)

func GetOptions(c *gin.Context) {
	var options []*model.Option
	config.OptionMapRWMutex.Lock()
	for k, v := range config.OptionMap {
		if strings.HasSuffix(k, "Token") || strings.HasSuffix(k, "Secret") {
			continue
		}
		options = append(options, &model.Option{
			Key:   k,
			Value: helper.Interface2String(v),
		})
	}
	config.OptionMapRWMutex.Unlock()
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    options,
	})
	return
}

func UpdateOption(c *gin.Context) {
	var option model.Option
	err := json.NewDecoder(c.Request.Body).Decode(&option)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"message": i18n.Translate(c, "invalid_parameter"),
		})
		return
	}
	switch option.Key {
	case "Theme":
		if !config.ValidThemes[option.Value] {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "无效的主题",
			})
			return
		}
	case "GitHubOAuthEnabled":
		if option.Value == "true" && config.GitHubClientId == "" {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "无法启用 GitHub OAuth,请先填入 GitHub Client Id 以及 GitHub Client Secret!",
			})
			return
		}
	case "EmailDomainRestrictionEnabled":
		if option.Value == "true" && len(config.EmailDomainWhitelist) == 0 {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "无法启用邮箱域名限制,请先填入限制的邮箱域名!",
			})
			return
		}
	case "WeChatAuthEnabled":
		if option.Value == "true" && config.WeChatServerAddress == "" {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "无法启用微信登录,请先填入微信登录相关配置信息!",
			})
			return
		}
	case "TurnstileCheckEnabled":
		if option.Value == "true" && config.TurnstileSiteKey == "" {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "无法启用 Turnstile 校验,请先填入 Turnstile 校验相关配置信息!",
			})
			return
		}
	}
	err = model.UpdateOption(option.Key, option.Value)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}


================================================
FILE: controller/redemption.go
================================================
package controller

import (
	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/common/helper"
	"github.com/songquanpeng/one-api/common/random"
	"github.com/songquanpeng/one-api/model"
	"net/http"
	"strconv"
)

func GetAllRedemptions(c *gin.Context) {
	p, _ := strconv.Atoi(c.Query("p"))
	if p < 0 {
		p = 0
	}
	redemptions, err := model.GetAllRedemptions(p*config.ItemsPerPage, config.ItemsPerPage)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    redemptions,
	})
	return
}

func SearchRedemptions(c *gin.Context) {
	keyword := c.Query("keyword")
	redemptions, err := model.SearchRedemptions(keyword)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    redemptions,
	})
	return
}

func GetRedemption(c *gin.Context) {
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	redemption, err := model.GetRedemptionById(id)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    redemption,
	})
	return
}

func AddRedemption(c *gin.Context) {
	redemption := model.Redemption{}
	err := c.ShouldBindJSON(&redemption)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	if len(redemption.Name) == 0 || len(redemption.Name) > 20 {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "兑换码名称长度必须在1-20之间",
		})
		return
	}
	if redemption.Count <= 0 {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "兑换码个数必须大于0",
		})
		return
	}
	if redemption.Count > 100 {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": "一次兑换码批量生成的个数不能大于 100",
		})
		return
	}
	var keys []string
	for i := 0; i < redemption.Count; i++ {
		key := random.GetUUID()
		cleanRedemption := model.Redemption{
			UserId:      c.GetInt(ctxkey.Id),
			Name:        redemption.Name,
			Key:         key,
			CreatedTime: helper.GetTimestamp(),
			Quota:       redemption.Quota,
		}
		err = cleanRedemption.Insert()
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": err.Error(),
				"data":    keys,
			})
			return
		}
		keys = append(keys, key)
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    keys,
	})
	return
}

func DeleteRedemption(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	err := model.DeleteRedemptionById(id)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}

func UpdateRedemption(c *gin.Context) {
	statusOnly := c.Query("status_only")
	redemption := model.Redemption{}
	err := c.ShouldBindJSON(&redemption)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	cleanRedemption, err := model.GetRedemptionById(redemption.Id)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	if statusOnly != "" {
		cleanRedemption.Status = redemption.Status
	} else {
		// If you add more fields, please also update redemption.Update()
		cleanRedemption.Name = redemption.Name
		cleanRedemption.Quota = redemption.Quota
	}
	err = cleanRedemption.Update()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    cleanRedemption,
	})
	return
}


================================================
FILE: controller/relay.go
================================================
package controller

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/common/helper"
	"github.com/songquanpeng/one-api/common/logger"
	"github.com/songquanpeng/one-api/middleware"
	dbmodel "github.com/songquanpeng/one-api/model"
	"github.com/songquanpeng/one-api/monitor"
	"github.com/songquanpeng/one-api/relay/controller"
	"github.com/songquanpeng/one-api/relay/model"
	"github.com/songquanpeng/one-api/relay/relaymode"
)

// https://platform.openai.com/docs/api-reference/chat

func relayHelper(c *gin.Context, relayMode int) *model.ErrorWithStatusCode {
	var err *model.ErrorWithStatusCode
	switch relayMode {
	case relaymode.ImagesGenerations:
		err = controller.RelayImageHelper(c, relayMode)
	case relaymode.AudioSpeech:
		fallthrough
	case relaymode.AudioTranslation:
		fallthrough
	case relaymode.AudioTranscription:
		err = controller.RelayAudioHelper(c, relayMode)
	case relaymode.Proxy:
		err = controller.RelayProxyHelper(c, relayMode)
	default:
		err = controller.RelayTextHelper(c)
	}
	return err
}

func Relay(c *gin.Context) {
	ctx := c.Request.Context()
	relayMode := relaymode.GetByPath(c.Request.URL.Path)
	if config.DebugEnabled {
		requestBody, _ := common.GetRequestBody(c)
		logger.Debugf(ctx, "request body: %s", string(requestBody))
	}
	channelId := c.GetInt(ctxkey.ChannelId)
	userId := c.GetInt(ctxkey.Id)
	bizErr := relayHelper(c, relayMode)
	if bizErr == nil {
		monitor.Emit(channelId, true)
		return
	}
	lastFailedChannelId := channelId
	channelName := c.GetString(ctxkey.ChannelName)
	group := c.GetString(ctxkey.Group)
	originalModel := c.GetString(ctxkey.OriginalModel)
	go processChannelRelayError(ctx, userId, channelId, channelName, *bizErr)
	requestId := c.GetString(helper.RequestIdKey)
	retryTimes := config.RetryTimes
	if !shouldRetry(c, bizErr.StatusCode) {
		logger.Errorf(ctx, "relay error happen, status code is %d, won't retry in this case", bizErr.StatusCode)
		retryTimes = 0
	}
	for i := retryTimes; i > 0; i-- {
		channel, err := dbmodel.CacheGetRandomSatisfiedChannel(group, originalModel, i != retryTimes)
		if err != nil {
			logger.Errorf(ctx, "CacheGetRandomSatisfiedChannel failed: %+v", err)
			break
		}
		logger.Infof(ctx, "using channel #%d to retry (remain times %d)", channel.Id, i)
		if channel.Id == lastFailedChannelId {
			continue
		}
		middleware.SetupContextForSelectedChannel(c, channel, originalModel)
		requestBody, err := common.GetRequestBody(c)
		c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
		bizErr = relayHelper(c, relayMode)
		if bizErr == nil {
			return
		}
		channelId := c.GetInt(ctxkey.ChannelId)
		lastFailedChannelId = channelId
		channelName := c.GetString(ctxkey.ChannelName)
		go processChannelRelayError(ctx, userId, channelId, channelName, *bizErr)
	}
	if bizErr != nil {
		if bizErr.StatusCode == http.StatusTooManyRequests {
			bizErr.Error.Message = "当前分组上游负载已饱和,请稍后再试"
		}

		// BUG: bizErr is in race condition
		bizErr.Error.Message = helper.MessageWithRequestId(bizErr.Error.Message, requestId)
		c.JSON(bizErr.StatusCode, gin.H{
			"error": bizErr.Error,
		})
	}
}

func shouldRetry(c *gin.Context, statusCode int) bool {
	if _, ok := c.Get(ctxkey.SpecificChannelId); ok {
		return false
	}
	if statusCode == http.StatusTooManyRequests {
		return true
	}
	if statusCode/100 == 5 {
		return true
	}
	if statusCode == http.StatusBadRequest {
		return false
	}
	if statusCode/100 == 2 {
		return false
	}
	return true
}

func processChannelRelayError(ctx context.Context, userId int, channelId int, channelName string, err model.ErrorWithStatusCode) {
	logger.Errorf(ctx, "relay error (channel id %d, user id: %d): %s", channelId, userId, err.Message)
	// https://platform.openai.com/docs/guides/error-codes/api-errors
	if monitor.ShouldDisableChannel(&err.Error, err.StatusCode) {
		monitor.DisableChannel(channelId, channelName, err.Message)
	} else {
		monitor.Emit(channelId, false)
	}
}

func RelayNotImplemented(c *gin.Context) {
	err := model.Error{
		Message: "API not implemented",
		Type:    "one_api_error",
		Param:   "",
		Code:    "api_not_implemented",
	}
	c.JSON(http.StatusNotImplemented, gin.H{
		"error": err,
	})
}

func RelayNotFound(c *gin.Context) {
	err := model.Error{
		Message: fmt.Sprintf("Invalid URL (%s %s)", c.Request.Method, c.Request.URL.Path),
		Type:    "invalid_request_error",
		Param:   "",
		Code:    "",
	}
	c.JSON(http.StatusNotFound, gin.H{
		"error": err,
	})
}


================================================
FILE: controller/token.go
================================================
package controller

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/common/helper"
	"github.com/songquanpeng/one-api/common/network"
	"github.com/songquanpeng/one-api/common/random"
	"github.com/songquanpeng/one-api/model"
	"net/http"
	"strconv"
)

func GetAllTokens(c *gin.Context) {
	userId := c.GetInt(ctxkey.Id)
	p, _ := strconv.Atoi(c.Query("p"))
	if p < 0 {
		p = 0
	}

	order := c.Query("order")
	tokens, err := model.GetAllUserTokens(userId, p*config.ItemsPerPage, config.ItemsPerPage, order)

	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    tokens,
	})
	return
}

func SearchTokens(c *gin.Context) {
	userId := c.GetInt(ctxkey.Id)
	keyword := c.Query("keyword")
	tokens, err := model.SearchUserTokens(userId, keyword)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    tokens,
	})
	return
}

func GetToken(c *gin.Context) {
	id, err := strconv.Atoi(c.Param("id"))
	userId := c.GetInt(ctxkey.Id)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	token, err := model.GetTokenByIds(id, userId)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    token,
	})
	return
}

func GetTokenStatus(c *gin.Context) {
	tokenId := c.GetInt(ctxkey.TokenId)
	userId := c.GetInt(ctxkey.Id)
	token, err := model.GetTokenByIds(tokenId, userId)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	expiredAt := token.ExpiredTime
	if expiredAt == -1 {
		expiredAt = 0
	}
	c.JSON(http.StatusOK, gin.H{
		"object":          "credit_summary",
		"total_granted":   token.RemainQuota,
		"total_used":      0, // not supported currently
		"total_available": token.RemainQuota,
		"expires_at":      expiredAt * 1000,
	})
}

func validateToken(c *gin.Context, token model.Token) error {
	if len(token.Name) > 30 {
		return fmt.Errorf("令牌名称过长")
	}
	if token.Subnet != nil && *token.Subnet != "" {
		err := network.IsValidSubnets(*token.Subnet)
		if err != nil {
			return fmt.Errorf("无效的网段:%s", err.Error())
		}
	}
	return nil
}

func AddToken(c *gin.Context) {
	token := model.Token{}
	err := c.ShouldBindJSON(&token)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	err = validateToken(c, token)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": fmt.Sprintf("参数错误:%s", err.Error()),
		})
		return
	}

	cleanToken := model.Token{
		UserId:         c.GetInt(ctxkey.Id),
		Name:           token.Name,
		Key:            random.GenerateKey(),
		CreatedTime:    helper.GetTimestamp(),
		AccessedTime:   helper.GetTimestamp(),
		ExpiredTime:    token.ExpiredTime,
		RemainQuota:    token.RemainQuota,
		UnlimitedQuota: token.UnlimitedQuota,
		Models:         token.Models,
		Subnet:         token.Subnet,
	}
	err = cleanToken.Insert()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    cleanToken,
	})
	return
}

func DeleteToken(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	userId := c.GetInt(ctxkey.Id)
	err := model.DeleteTokenById(id, userId)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
	})
	return
}

func UpdateToken(c *gin.Context) {
	userId := c.GetInt(ctxkey.Id)
	statusOnly := c.Query("status_only")
	token := model.Token{}
	err := c.ShouldBindJSON(&token)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	err = validateToken(c, token)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": fmt.Sprintf("参数错误:%s", err.Error()),
		})
		return
	}
	cleanToken, err := model.GetTokenByIds(token.Id, userId)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	if token.Status == model.TokenStatusEnabled {
		if cleanToken.Status == model.TokenStatusExpired && cleanToken.ExpiredTime <= helper.GetTimestamp() && cleanToken.ExpiredTime != -1 {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期",
			})
			return
		}
		if cleanToken.Status == model.TokenStatusExhausted && cleanToken.RemainQuota <= 0 && !cleanToken.UnlimitedQuota {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度",
			})
			return
		}
	}
	if statusOnly != "" {
		cleanToken.Status = token.Status
	} else {
		// If you add more fields, please also update token.Update()
		cleanToken.Name = token.Name
		cleanToken.ExpiredTime = token.ExpiredTime
		cleanToken.RemainQuota = token.RemainQuota
		cleanToken.UnlimitedQuota = token.UnlimitedQuota
		cleanToken.Models = token.Models
		cleanToken.Subnet = token.Subnet
	}
	err = cleanToken.Update()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    cleanToken,
	})
	return
}


================================================
FILE: controller/user.go
================================================
package controller

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"

	"github.com/songquanpeng/one-api/common"
	"github.com/songquanpeng/one-api/common/config"
	"github.com/songquanpeng/one-api/common/ctxkey"
	"github.com/songquanpeng/one-api/common/i18n"
	"github.com/songquanpeng/one-api/common/random"
	"github.com/songquanpeng/one-api/model"
)

type LoginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

func Login(c *gin.Context) {
	if !config.PasswordLoginEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "管理员关闭了密码登录",
			"success": false,
		})
		return
	}
	var loginRequest LoginRequest
	err := json.NewDecoder(c.Request.Body).Decode(&loginRequest)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"message": i18n.Translate(c, "invalid_parameter"),
			"success": false,
		})
		return
	}
	username := loginRequest.Username
	password := loginRequest.Password
	if username == "" || password == "" {
		c.JSON(http.StatusOK, gin.H{
			"message": i18n.Translate(c, "invalid_parameter"),
			"success": false,
		})
		return
	}
	user := model.User{
		Username: username,
		Password: password,
	}
	err = user.ValidateAndFill()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"message": err.Error(),
			"success": false,
		})
		return
	}
	SetupLogin(&user, c)
}

// setup session & cookies and then return user info
func SetupLogin(user *model.User, c *gin.Context) {
	session := sessions.Default(c)
	session.Set("id", user.Id)
	session.Set("username", user.Username)
	session.Set("role", user.Role)
	session.Set("status", user.Status)
	err := session.Save()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"message": "无法保存会话信息,请重试",
			"success": false,
		})
		return
	}
	cleanUser := model.User{
		Id:          user.Id,
		Username:    user.Username,
		DisplayName: user.DisplayName,
		Role:        user.Role,
		Status:      user.Status,
	}
	c.JSON(http.StatusOK, gin.H{
		"message": "",
		"success": true,
		"data":    cleanUser,
	})
}

func Logout(c *gin.Context) {
	session := sessions.Default(c)
	session.Clear()
	err := session.Save()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"message": err.Error(),
			"success": false,
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"message": "",
		"success": true,
	})
}

func Register(c *gin.Context) {
	ctx := c.Request.Context()
	if !config.RegisterEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "管理员关闭了新用户注册",
			"success": false,
		})
		return
	}
	if !config.PasswordRegisterEnabled {
		c.JSON(http.StatusOK, gin.H{
			"message": "管理员关闭了通过密码进行注册,请使用第三方账户验证的形式进行注册",
			"success": false,
		})
		return
	}
	var user model.User
	err := json.NewDecoder(c.Request.Body).Decode(&user)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": i18n.Translate(c, "invalid_parameter"),
		})
		return
	}
	if err := common.Validate.Struct(&user); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"message": i18n.Translate(c, "invalid_input"),
		})
		return
	}
	if config.EmailVerificationEnabled {
		if user.Email == "" || user.VerificationCode == "" {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "管理员开启了邮箱验证,请输入邮箱地址和验证码",
			})
			return
		}
		if !common.VerifyCodeWithKey(user.Email, user.VerificationCode, common.EmailVerificationPurpose) {
			c.JSON(http.StatusOK, gin.H{
				"success": false,
				"message": "验证码错误或已过期",
			})
			return
		}
	}
	affCode := user.AffCode // this code is the inviter's code, not the user's own 
Download .txt
gitextract_g67kz3s3/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   └── workflows/
│       ├── ci.yml
│       ├── docker-image.yml
│       ├── linux-release.yml
│       ├── macos-release.yml
│       └── windows-release.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.en.md
├── README.ja.md
├── README.md
├── VERSION
├── bin/
│   ├── migration_v0.2-v0.3.sql
│   ├── migration_v0.3-v0.4.sql
│   └── time_test.sh
├── common/
│   ├── blacklist/
│   │   └── main.go
│   ├── client/
│   │   └── init.go
│   ├── config/
│   │   └── config.go
│   ├── constants.go
│   ├── conv/
│   │   └── any.go
│   ├── crypto.go
│   ├── ctxkey/
│   │   └── key.go
│   ├── custom-event.go
│   ├── database.go
│   ├── embed-file-system.go
│   ├── env/
│   │   └── helper.go
│   ├── gin.go
│   ├── helper/
│   │   ├── helper.go
│   │   ├── key.go
│   │   └── time.go
│   ├── i18n/
│   │   ├── i18n.go
│   │   └── locales/
│   │       ├── en.json
│   │       └── zh-CN.json
│   ├── image/
│   │   ├── image.go
│   │   └── image_test.go
│   ├── init.go
│   ├── logger/
│   │   ├── constants.go
│   │   └── logger.go
│   ├── message/
│   │   ├── email.go
│   │   ├── main.go
│   │   ├── message-pusher.go
│   │   └── template.go
│   ├── network/
│   │   ├── ip.go
│   │   └── ip_test.go
│   ├── random/
│   │   └── main.go
│   ├── rate-limit.go
│   ├── redis.go
│   ├── render/
│   │   └── render.go
│   ├── utils/
│   │   └── array.go
│   ├── utils.go
│   ├── validate.go
│   └── verification.go
├── controller/
│   ├── auth/
│   │   ├── github.go
│   │   ├── lark.go
│   │   ├── oidc.go
│   │   └── wechat.go
│   ├── billing.go
│   ├── channel-billing.go
│   ├── channel-test.go
│   ├── channel.go
│   ├── group.go
│   ├── log.go
│   ├── misc.go
│   ├── model.go
│   ├── option.go
│   ├── redemption.go
│   ├── relay.go
│   ├── token.go
│   └── user.go
├── docker-compose.yml
├── docs/
│   └── API.md
├── go.mod
├── go.sum
├── main.go
├── middleware/
│   ├── auth.go
│   ├── cache.go
│   ├── cors.go
│   ├── distributor.go
│   ├── gzip.go
│   ├── language.go
│   ├── logger.go
│   ├── rate-limit.go
│   ├── recover.go
│   ├── request-id.go
│   ├── turnstile-check.go
│   └── utils.go
├── model/
│   ├── ability.go
│   ├── cache.go
│   ├── channel.go
│   ├── log.go
│   ├── main.go
│   ├── option.go
│   ├── redemption.go
│   ├── token.go
│   ├── user.go
│   └── utils.go
├── monitor/
│   ├── channel.go
│   ├── manage.go
│   └── metric.go
├── one-api.service
├── pull_request_template.md
├── relay/
│   ├── adaptor/
│   │   ├── ai360/
│   │   │   └── constants.go
│   │   ├── aiproxy/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── ali/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── image.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── alibailian/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── anthropic/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── aws/
│   │   │   ├── adaptor.go
│   │   │   ├── claude/
│   │   │   │   ├── adapter.go
│   │   │   │   ├── main.go
│   │   │   │   └── model.go
│   │   │   ├── llama3/
│   │   │   │   ├── adapter.go
│   │   │   │   ├── main.go
│   │   │   │   ├── main_test.go
│   │   │   │   └── model.go
│   │   │   ├── registry.go
│   │   │   └── utils/
│   │   │       ├── adaptor.go
│   │   │       └── utils.go
│   │   ├── baichuan/
│   │   │   └── constants.go
│   │   ├── baidu/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── baiduv2/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── cloudflare/
│   │   │   ├── adaptor.go
│   │   │   ├── constant.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── cohere/
│   │   │   ├── adaptor.go
│   │   │   ├── constant.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── common.go
│   │   ├── coze/
│   │   │   ├── adaptor.go
│   │   │   ├── constant/
│   │   │   │   ├── contenttype/
│   │   │   │   │   └── define.go
│   │   │   │   ├── event/
│   │   │   │   │   └── define.go
│   │   │   │   └── messagetype/
│   │   │   │       └── define.go
│   │   │   ├── constants.go
│   │   │   ├── helper.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── deepl/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── helper.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── deepseek/
│   │   │   └── constants.go
│   │   ├── doubao/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── gemini/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── geminiv2/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── groq/
│   │   │   └── constants.go
│   │   ├── interface.go
│   │   ├── lingyiwanwu/
│   │   │   └── constants.go
│   │   ├── minimax/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── mistral/
│   │   │   └── constants.go
│   │   ├── moonshot/
│   │   │   └── constants.go
│   │   ├── novita/
│   │   │   ├── constants.go
│   │   │   └── main.go
│   │   ├── ollama/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── openai/
│   │   │   ├── adaptor.go
│   │   │   ├── compatible.go
│   │   │   ├── constants.go
│   │   │   ├── helper.go
│   │   │   ├── image.go
│   │   │   ├── main.go
│   │   │   ├── model.go
│   │   │   ├── token.go
│   │   │   └── util.go
│   │   ├── openrouter/
│   │   │   └── constants.go
│   │   ├── palm/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── model.go
│   │   │   └── palm.go
│   │   ├── proxy/
│   │   │   └── adaptor.go
│   │   ├── replicate/
│   │   │   ├── adaptor.go
│   │   │   ├── chat.go
│   │   │   ├── constant.go
│   │   │   ├── image.go
│   │   │   └── model.go
│   │   ├── siliconflow/
│   │   │   └── constants.go
│   │   ├── stepfun/
│   │   │   └── constants.go
│   │   ├── tencent/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── togetherai/
│   │   │   └── constants.go
│   │   ├── vertexai/
│   │   │   ├── adaptor.go
│   │   │   ├── claude/
│   │   │   │   ├── adapter.go
│   │   │   │   └── model.go
│   │   │   ├── gemini/
│   │   │   │   └── adapter.go
│   │   │   ├── registry.go
│   │   │   └── token.go
│   │   ├── xai/
│   │   │   └── constants.go
│   │   ├── xunfei/
│   │   │   ├── adaptor.go
│   │   │   ├── constants.go
│   │   │   ├── domain.go
│   │   │   ├── main.go
│   │   │   └── model.go
│   │   ├── xunfeiv2/
│   │   │   └── constants.go
│   │   └── zhipu/
│   │       ├── adaptor.go
│   │       ├── constants.go
│   │       ├── main.go
│   │       └── model.go
│   ├── adaptor.go
│   ├── adaptor_test.go
│   ├── apitype/
│   │   └── define.go
│   ├── billing/
│   │   ├── billing.go
│   │   └── ratio/
│   │       ├── group.go
│   │       ├── image.go
│   │       └── model.go
│   ├── channeltype/
│   │   ├── define.go
│   │   ├── helper.go
│   │   ├── url.go
│   │   └── url_test.go
│   ├── constant/
│   │   ├── common.go
│   │   ├── finishreason/
│   │   │   └── define.go
│   │   └── role/
│   │       └── define.go
│   ├── controller/
│   │   ├── audio.go
│   │   ├── error.go
│   │   ├── helper.go
│   │   ├── image.go
│   │   ├── proxy.go
│   │   ├── text.go
│   │   └── validator/
│   │       └── validation.go
│   ├── meta/
│   │   └── relay_meta.go
│   ├── model/
│   │   ├── constant.go
│   │   ├── general.go
│   │   ├── image.go
│   │   ├── message.go
│   │   ├── misc.go
│   │   └── tool.go
│   └── relaymode/
│       ├── define.go
│       └── helper.go
├── router/
│   ├── api.go
│   ├── dashboard.go
│   ├── main.go
│   ├── relay.go
│   └── web.go
└── web/
    ├── README.md
    ├── THEMES
    ├── air/
    │   ├── .gitignore
    │   ├── README.md
    │   ├── package.json
    │   ├── public/
    │   │   ├── index.html
    │   │   └── robots.txt
    │   ├── src/
    │   │   ├── App.js
    │   │   ├── components/
    │   │   │   ├── ChannelsTable.js
    │   │   │   ├── Footer.js
    │   │   │   ├── GitHubOAuth.js
    │   │   │   ├── HeaderBar.js
    │   │   │   ├── Loading.js
    │   │   │   ├── LoginForm.js
    │   │   │   ├── LogsTable.js
    │   │   │   ├── MjLogsTable.js
    │   │   │   ├── OperationSetting.js
    │   │   │   ├── OtherSetting.js
    │   │   │   ├── PasswordResetConfirm.js
    │   │   │   ├── PasswordResetForm.js
    │   │   │   ├── PersonalSetting.js
    │   │   │   ├── PrivateRoute.js
    │   │   │   ├── RedemptionsTable.js
    │   │   │   ├── RegisterForm.js
    │   │   │   ├── SiderBar.js
    │   │   │   ├── SystemSetting.js
    │   │   │   ├── TokensTable.js
    │   │   │   ├── UsersTable.js
    │   │   │   ├── WeChatIcon.js
    │   │   │   └── utils.js
    │   │   ├── constants/
    │   │   │   ├── channel.constants.js
    │   │   │   ├── common.constant.js
    │   │   │   ├── index.js
    │   │   │   ├── toast.constants.js
    │   │   │   └── user.constants.js
    │   │   ├── context/
    │   │   │   ├── Status/
    │   │   │   │   ├── index.js
    │   │   │   │   └── reducer.js
    │   │   │   └── User/
    │   │   │       ├── index.js
    │   │   │       └── reducer.js
    │   │   ├── helpers/
    │   │   │   ├── api.js
    │   │   │   ├── auth-header.js
    │   │   │   ├── history.js
    │   │   │   ├── index.js
    │   │   │   ├── render.js
    │   │   │   └── utils.js
    │   │   ├── index.css
    │   │   ├── index.js
    │   │   └── pages/
    │   │       ├── About/
    │   │       │   └── index.js
    │   │       ├── Channel/
    │   │       │   ├── EditChannel.js
    │   │       │   └── index.js
    │   │       ├── Chat/
    │   │       │   └── index.js
    │   │       ├── Detail/
    │   │       │   └── index.js
    │   │       ├── Home/
    │   │       │   └── index.js
    │   │       ├── Log/
    │   │       │   └── index.js
    │   │       ├── Midjourney/
    │   │       │   └── index.js
    │   │       ├── NotFound/
    │   │       │   └── index.js
    │   │       ├── Redemption/
    │   │       │   ├── EditRedemption.js
    │   │       │   └── index.js
    │   │       ├── Setting/
    │   │       │   └── index.js
    │   │       ├── Token/
    │   │       │   ├── EditToken.js
    │   │       │   └── index.js
    │   │       ├── TopUp/
    │   │       │   └── index.js
    │   │       └── User/
    │   │           ├── AddUser.js
    │   │           ├── EditUser.js
    │   │           └── index.js
    │   └── vercel.json
    ├── berry/
    │   ├── .gitignore
    │   ├── .prettierrc
    │   ├── README.md
    │   ├── jsconfig.json
    │   ├── package.json
    │   ├── public/
    │   │   └── index.html
    │   └── src/
    │       ├── App.js
    │       ├── assets/
    │       │   └── scss/
    │       │       ├── _themes-vars.module.scss
    │       │       ├── fonts.scss
    │       │       └── style.scss
    │       ├── config.js
    │       ├── constants/
    │       │   ├── ChannelConstants.js
    │       │   ├── CommonConstants.js
    │       │   ├── SnackbarConstants.js
    │       │   └── index.js
    │       ├── contexts/
    │       │   ├── StatusContext.js
    │       │   └── UserContext.js
    │       ├── hooks/
    │       │   ├── useAuth.js
    │       │   ├── useLogin.js
    │       │   ├── useRegister.js
    │       │   └── useScriptRef.js
    │       ├── index.js
    │       ├── layout/
    │       │   ├── MainLayout/
    │       │   │   ├── Header/
    │       │   │   │   ├── ProfileSection/
    │       │   │   │   │   └── index.js
    │       │   │   │   └── index.js
    │       │   │   ├── LogoSection/
    │       │   │   │   └── index.js
    │       │   │   ├── Sidebar/
    │       │   │   │   ├── MenuCard/
    │       │   │   │   │   └── index.js
    │       │   │   │   ├── MenuList/
    │       │   │   │   │   ├── NavCollapse/
    │       │   │   │   │   │   └── index.js
    │       │   │   │   │   ├── NavGroup/
    │       │   │   │   │   │   └── index.js
    │       │   │   │   │   ├── NavItem/
    │       │   │   │   │   │   └── index.js
    │       │   │   │   │   └── index.js
    │       │   │   │   └── index.js
    │       │   │   └── index.js
    │       │   ├── MinimalLayout/
    │       │   │   ├── Header/
    │       │   │   │   └── index.js
    │       │   │   └── index.js
    │       │   ├── NavMotion.js
    │       │   └── NavigationScroll.js
    │       ├── menu-items/
    │       │   ├── index.js
    │       │   └── panel.js
    │       ├── routes/
    │       │   ├── MainRoutes.js
    │       │   ├── OtherRoutes.js
    │       │   └── index.js
    │       ├── serviceWorker.js
    │       ├── store/
    │       │   ├── accountReducer.js
    │       │   ├── actions.js
    │       │   ├── constant.js
    │       │   ├── customizationReducer.js
    │       │   ├── index.js
    │       │   ├── reducer.js
    │       │   └── siteInfoReducer.js
    │       ├── themes/
    │       │   ├── compStyleOverride.js
    │       │   ├── index.js
    │       │   ├── palette.js
    │       │   └── typography.js
    │       ├── ui-component/
    │       │   ├── AdminContainer.js
    │       │   ├── Footer.js
    │       │   ├── Label.js
    │       │   ├── Loadable.js
    │       │   ├── Loader.js
    │       │   ├── Logo.js
    │       │   ├── SvgColor.js
    │       │   ├── Switch.js
    │       │   ├── TableToolBar.js
    │       │   ├── ThemeButton.js
    │       │   ├── cards/
    │       │   │   ├── CardSecondaryAction.js
    │       │   │   ├── MainCard.js
    │       │   │   ├── Skeleton/
    │       │   │   │   ├── EarningCard.js
    │       │   │   │   ├── ImagePlaceholder.js
    │       │   │   │   ├── PopularCard.js
    │       │   │   │   ├── ProductPlaceholder.js
    │       │   │   │   ├── TotalGrowthBarChart.js
    │       │   │   │   └── TotalIncomeCard.js
    │       │   │   ├── SubCard.js
    │       │   │   └── UserCard.js
    │       │   └── extended/
    │       │       ├── AnimateButton.js
    │       │       ├── Avatar.js
    │       │       ├── Breadcrumbs.js
    │       │       └── Transitions.js
    │       ├── utils/
    │       │   ├── api.js
    │       │   ├── chart.js
    │       │   ├── common.js
    │       │   ├── password-strength.js
    │       │   └── route-guard/
    │       │       └── AuthGuard.js
    │       └── views/
    │           ├── About/
    │           │   └── index.js
    │           ├── Authentication/
    │           │   ├── Auth/
    │           │   │   ├── ForgetPassword.js
    │           │   │   ├── GitHubOAuth.js
    │           │   │   ├── LarkOAuth.js
    │           │   │   ├── Login.js
    │           │   │   ├── OidcOAuth.js
    │           │   │   ├── Register.js
    │           │   │   └── ResetPassword.js
    │           │   ├── AuthCardWrapper.js
    │           │   ├── AuthForms/
    │           │   │   ├── AuthLogin.js
    │           │   │   ├── AuthRegister.js
    │           │   │   ├── ForgetPasswordForm.js
    │           │   │   ├── ResetPasswordForm.js
    │           │   │   └── WechatModal.js
    │           │   └── AuthWrapper.js
    │           ├── Channel/
    │           │   ├── component/
    │           │   │   ├── EditModal.js
    │           │   │   ├── GroupLabel.js
    │           │   │   ├── NameLabel.js
    │           │   │   ├── ResponseTimeLabel.js
    │           │   │   ├── TableHead.js
    │           │   │   └── TableRow.js
    │           │   ├── index.js
    │           │   └── type/
    │           │       └── Config.js
    │           ├── Dashboard/
    │           │   ├── component/
    │           │   │   ├── StatisticalBarChart.js
    │           │   │   ├── StatisticalCard.js
    │           │   │   └── StatisticalLineChartCard.js
    │           │   └── index.js
    │           ├── Error/
    │           │   └── index.js
    │           ├── Home/
    │           │   ├── baseIndex.js
    │           │   └── index.js
    │           ├── Log/
    │           │   ├── component/
    │           │   │   ├── TableHead.js
    │           │   │   ├── TableRow.js
    │           │   │   └── TableToolBar.js
    │           │   ├── index.js
    │           │   └── type/
    │           │       └── LogType.js
    │           ├── Profile/
    │           │   ├── component/
    │           │   │   └── EmailModal.js
    │           │   └── index.js
    │           ├── Redemption/
    │           │   ├── component/
    │           │   │   ├── EditModal.js
    │           │   │   ├── TableHead.js
    │           │   │   └── TableRow.js
    │           │   └── index.js
    │           ├── Setting/
    │           │   ├── component/
    │           │   │   ├── OperationSetting.js
    │           │   │   ├── OtherSetting.js
    │           │   │   └── SystemSetting.js
    │           │   └── index.js
    │           ├── Token/
    │           │   ├── component/
    │           │   │   ├── EditModal.js
    │           │   │   ├── TableHead.js
    │           │   │   └── TableRow.js
    │           │   └── index.js
    │           ├── Topup/
    │           │   ├── component/
    │           │   │   ├── InviteCard.js
    │           │   │   └── TopupCard.js
    │           │   └── index.js
    │           └── User/
    │               ├── component/
    │               │   ├── EditModal.js
    │               │   ├── TableHead.js
    │               │   └── TableRow.js
    │               └── index.js
    ├── build.sh
    └── default/
        ├── .gitignore
        ├── README.md
        ├── package.json
        ├── public/
        │   ├── index.html
        │   └── robots.txt
        ├── src/
        │   ├── App.js
        │   ├── components/
        │   │   ├── ChannelsTable.js
        │   │   ├── Footer.js
        │   │   ├── GitHubOAuth.js
        │   │   ├── Header.js
        │   │   ├── LarkOAuth.js
        │   │   ├── Loading.js
        │   │   ├── LoginForm.js
        │   │   ├── LogsTable.js
        │   │   ├── OperationSetting.js
        │   │   ├── OtherSetting.js
        │   │   ├── PasswordResetConfirm.js
        │   │   ├── PasswordResetForm.js
        │   │   ├── PersonalSetting.js
        │   │   ├── PrivateRoute.js
        │   │   ├── RedemptionsTable.js
        │   │   ├── RegisterForm.js
        │   │   ├── SystemSetting.js
        │   │   ├── TokensTable.js
        │   │   ├── UsersTable.js
        │   │   └── utils.js
        │   ├── constants/
        │   │   ├── channel.constants.js
        │   │   ├── common.constant.js
        │   │   ├── index.js
        │   │   ├── toast.constants.js
        │   │   └── user.constants.js
        │   ├── context/
        │   │   ├── Status/
        │   │   │   ├── index.js
        │   │   │   └── reducer.js
        │   │   └── User/
        │   │       ├── index.js
        │   │       └── reducer.js
        │   ├── helpers/
        │   │   ├── api.js
        │   │   ├── auth-header.js
        │   │   ├── helper.js
        │   │   ├── history.js
        │   │   ├── index.js
        │   │   ├── render.js
        │   │   └── utils.js
        │   ├── i18n.js
        │   ├── index.css
        │   ├── index.js
        │   ├── locales/
        │   │   ├── en/
        │   │   │   └── translation.json
        │   │   └── zh/
        │   │       └── translation.json
        │   └── pages/
        │       ├── About/
        │       │   └── index.js
        │       ├── Channel/
        │       │   ├── EditChannel.js
        │       │   └── index.js
        │       ├── Chat/
        │       │   └── index.js
        │       ├── Dashboard/
        │       │   ├── Dashboard.css
        │       │   └── index.js
        │       ├── Home/
        │       │   └── index.js
        │       ├── Log/
        │       │   └── index.js
        │       ├── NotFound/
        │       │   └── index.js
        │       ├── Redemption/
        │       │   ├── EditRedemption.js
        │       │   └── index.js
        │       ├── Setting/
        │       │   └── index.js
        │       ├── Token/
        │       │   ├── EditToken.js
        │       │   └── index.js
        │       ├── TopUp/
        │       │   └── index.js
        │       └── User/
        │           ├── AddUser.js
        │           ├── EditUser.js
        │           └── index.js
        └── vercel.json
Download .txt
SYMBOL INDEX (1460 symbols across 273 files)

FILE: common/blacklist/main.go
  function init (line 10) | func init() {
  function userId2Key (line 14) | func userId2Key(id int) string {
  function BanUser (line 18) | func BanUser(id int) {
  function UnbanUser (line 22) | func UnbanUser(id int) {
  function IsUserBanned (line 26) | func IsUserBanned(id int) bool {

FILE: common/client/init.go
  function Init (line 16) | func Init() {

FILE: common/conv/any.go
  function AsString (line 3) | func AsString(v any) string {

FILE: common/crypto.go
  function Password2Hash (line 5) | func Password2Hash(password string) (string, error) {
  function ValidatePasswordAndHash (line 11) | func ValidatePasswordAndHash(password string, hash string) bool {

FILE: common/ctxkey/key.go
  constant Config (line 4) | Config            = "config"
  constant Id (line 5) | Id                = "id"
  constant Username (line 6) | Username          = "username"
  constant Role (line 7) | Role              = "role"
  constant Status (line 8) | Status            = "status"
  constant Channel (line 9) | Channel           = "channel"
  constant ChannelId (line 10) | ChannelId         = "channel_id"
  constant SpecificChannelId (line 11) | SpecificChannelId = "specific_channel_id"
  constant RequestModel (line 12) | RequestModel      = "request_model"
  constant ConvertedRequest (line 13) | ConvertedRequest  = "converted_request"
  constant OriginalModel (line 14) | OriginalModel     = "original_model"
  constant Group (line 15) | Group             = "group"
  constant ModelMapping (line 16) | ModelMapping      = "model_mapping"
  constant ChannelName (line 17) | ChannelName       = "channel_name"
  constant TokenId (line 18) | TokenId           = "token_id"
  constant TokenName (line 19) | TokenName         = "token_name"
  constant BaseURL (line 20) | BaseURL           = "base_url"
  constant AvailableModels (line 21) | AvailableModels   = "available_models"
  constant KeyRequestBody (line 22) | KeyRequestBody    = "key_request_body"
  constant SystemPrompt (line 23) | SystemPrompt      = "system_prompt"

FILE: common/custom-event.go
  type stringWriter (line 14) | type stringWriter interface
  type stringWrapper (line 19) | type stringWrapper struct
    method writeString (line 23) | func (w stringWrapper) writeString(str string) (int, error) {
  function checkWriter (line 27) | func checkWriter(writer io.Writer) stringWriter {
  type CustomEvent (line 50) | type CustomEvent struct
    method Render (line 70) | func (r CustomEvent) Render(w http.ResponseWriter) error {
    method WriteContentType (line 75) | func (r CustomEvent) WriteContentType(w http.ResponseWriter) {
  function encode (line 57) | func encode(writer io.Writer, event CustomEvent) error {
  function writeData (line 62) | func writeData(w stringWriter, data interface{}) error {

FILE: common/embed-file-system.go
  type embedFileSystem (line 12) | type embedFileSystem struct
    method Exists (line 16) | func (e embedFileSystem) Exists(prefix string, path string) bool {
  function EmbedFolder (line 21) | func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSy...

FILE: common/env/helper.go
  function Bool (line 8) | func Bool(env string, defaultValue bool) bool {
  function Int (line 15) | func Int(env string, defaultValue int) int {
  function Float64 (line 26) | func Float64(env string, defaultValue float64) float64 {
  function String (line 37) | func String(env string, defaultValue string) string {

FILE: common/gin.go
  function GetRequestBody (line 13) | func GetRequestBody(c *gin.Context) ([]byte, error) {
  function UnmarshalBodyReusable (line 27) | func UnmarshalBodyReusable(c *gin.Context, v any) error {
  function SetEventStreamHeaders (line 47) | func SetEventStreamHeaders(c *gin.Context) {

FILE: common/helper/helper.go
  function OpenBrowser (line 19) | func OpenBrowser(url string) {
  function GetIp (line 35) | func GetIp() (ip string) {
  function Bytes2Size (line 66) | func Bytes2Size(num int64) string {
  function Interface2String (line 84) | func Interface2String(inter interface{}) string {
  function UnescapeHTML (line 96) | func UnescapeHTML(x string) interface{} {
  function IntMax (line 100) | func IntMax(a int, b int) int {
  function GenRequestID (line 108) | func GenRequestID() string {
  function SetRequestID (line 112) | func SetRequestID(ctx context.Context, id string) context.Context {
  function GetRequestID (line 116) | func GetRequestID(ctx context.Context) string {
  function GetResponseID (line 124) | func GetResponseID(c *gin.Context) string {
  function Max (line 129) | func Max(a int, b int) int {
  function AssignOrDefault (line 137) | func AssignOrDefault(value string, defaultValue string) string {
  function MessageWithRequestId (line 144) | func MessageWithRequestId(message string, id string) string {
  function String2Int (line 148) | func String2Int(str string) int {
  function Float64PtrMax (line 156) | func Float64PtrMax(p *float64, maxValue float64) *float64 {
  function Float64PtrMin (line 166) | func Float64PtrMin(p *float64, minValue float64) *float64 {

FILE: common/helper/key.go
  constant RequestIdKey (line 4) | RequestIdKey = "X-Oneapi-Request-Id"

FILE: common/helper/time.go
  function GetTimestamp (line 8) | func GetTimestamp() int64 {
  function GetTimeString (line 12) | func GetTimeString() string {
  function CalcElapsedTime (line 18) | func CalcElapsedTime(start time.Time) int64 {

FILE: common/i18n/i18n.go
  function Init (line 21) | func Init() error {
  function GetLang (line 48) | func GetLang(c *gin.Context) string {
  function Translate (line 60) | func Translate(c *gin.Context, message string) string {
  function translateHelper (line 65) | func translateHelper(lang, message string) string {

FILE: common/image/image.go
  function IsImageUrl (line 22) | func IsImageUrl(url string) (bool, error) {
  function GetImageSizeFromUrl (line 33) | func GetImageSizeFromUrl(url string) (width int, height int, err error) {
  function GetImageFromUrl (line 50) | func GetImageFromUrl(url string) (mimeType string, data string, err erro...
  function GetImageSizeFromBase64 (line 89) | func GetImageSizeFromBase64(encoded string) (width int, height int, err ...
  function GetImageSize (line 107) | func GetImageSize(image string) (width int, height int, err error) {

FILE: common/image/image_test.go
  type CountingReader (line 22) | type CountingReader struct
    method Read (line 27) | func (r *CountingReader) Read(p []byte) (n int, err error) {
  function TestMain (line 48) | func TestMain(m *testing.M) {
  function TestDecode (line 53) | func TestDecode(t *testing.T) {
  function TestBase64 (line 98) | func TestBase64(t *testing.T) {
  function TestGetImageSize (line 151) | func TestGetImageSize(t *testing.T) {
  function TestGetImageSizeFromBase64 (line 162) | func TestGetImageSizeFromBase64(t *testing.T) {

FILE: common/init.go
  function printHelp (line 20) | func printHelp() {
  function Init (line 27) | func Init() {

FILE: common/logger/logger.go
  type loggerLevel (line 21) | type loggerLevel
  constant loggerDEBUG (line 24) | loggerDEBUG loggerLevel = "DEBUG"
  constant loggerINFO (line 25) | loggerINFO  loggerLevel = "INFO"
  constant loggerWarn (line 26) | loggerWarn  loggerLevel = "WARN"
  constant loggerError (line 27) | loggerError loggerLevel = "ERROR"
  constant loggerFatal (line 28) | loggerFatal loggerLevel = "FATAL"
  function SetupLogger (line 33) | func SetupLogger() {
  function SysLog (line 52) | func SysLog(s string) {
  function SysLogf (line 56) | func SysLogf(format string, a ...any) {
  function SysWarn (line 60) | func SysWarn(s string) {
  function SysWarnf (line 64) | func SysWarnf(format string, a ...any) {
  function SysError (line 68) | func SysError(s string) {
  function SysErrorf (line 72) | func SysErrorf(format string, a ...any) {
  function Debug (line 76) | func Debug(ctx context.Context, msg string) {
  function Info (line 83) | func Info(ctx context.Context, msg string) {
  function Warn (line 87) | func Warn(ctx context.Context, msg string) {
  function Error (line 91) | func Error(ctx context.Context, msg string) {
  function Debugf (line 95) | func Debugf(ctx context.Context, format string, a ...any) {
  function Infof (line 102) | func Infof(ctx context.Context, format string, a ...any) {
  function Warnf (line 106) | func Warnf(ctx context.Context, format string, a ...any) {
  function Errorf (line 110) | func Errorf(ctx context.Context, format string, a ...any) {
  function FatalLog (line 114) | func FatalLog(s string) {
  function FatalLogf (line 118) | func FatalLogf(format string, a ...any) {
  function logHelper (line 122) | func logHelper(ctx context.Context, level loggerLevel, msg string) {
  function getLineInfo (line 143) | func getLineInfo() (string, string) {

FILE: common/message/email.go
  function shouldAuth (line 17) | func shouldAuth() bool {
  function SendEmail (line 21) | func SendEmail(subject string, receiver string, content string) error {

FILE: common/message/main.go
  constant ByAll (line 9) | ByAll           = "all"
  constant ByEmail (line 10) | ByEmail         = "email"
  constant ByMessagePusher (line 11) | ByMessagePusher = "message_pusher"
  function Notify (line 14) | func Notify(by string, title string, description string, content string)...

FILE: common/message/message-pusher.go
  type request (line 11) | type request struct
  type response (line 20) | type response struct
  function SendMessage (line 25) | func SendMessage(title string, description string, content string) error {

FILE: common/message/template.go
  function EmailTemplate (line 10) | func EmailTemplate(title, content string) string {

FILE: common/network/ip.go
  function splitSubnets (line 11) | func splitSubnets(subnets string) []string {
  function isValidSubnet (line 19) | func isValidSubnet(subnet string) error {
  function isIpInSubnet (line 27) | func isIpInSubnet(ctx context.Context, ip string, subnet string) bool {
  function IsValidSubnets (line 36) | func IsValidSubnets(subnets string) error {
  function IsIpInSubnets (line 45) | func IsIpInSubnets(ctx context.Context, ip string, subnets string) bool {

FILE: common/network/ip_test.go
  function TestIsIpInSubnet (line 10) | func TestIsIpInSubnet(t *testing.T) {

FILE: common/random/main.go
  function GetUUID (line 10) | func GetUUID() string {
  constant keyChars (line 16) | keyChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  constant keyNumbers (line 17) | keyNumbers = "0123456789"
  function init (line 19) | func init() {
  function GenerateKey (line 23) | func GenerateKey() string {
  function GetRandomString (line 40) | func GetRandomString(length int) string {
  function GetRandomNumberString (line 49) | func GetRandomNumberString(length int) string {
  function RandRange (line 59) | func RandRange(min, max int) int {

FILE: common/rate-limit.go
  type InMemoryRateLimiter (line 8) | type InMemoryRateLimiter struct
    method Init (line 14) | func (l *InMemoryRateLimiter) Init(expirationDuration time.Duration) {
    method clearExpiredItems (line 28) | func (l *InMemoryRateLimiter) clearExpiredItems() {
    method Request (line 45) | func (l *InMemoryRateLimiter) Request(key string, maxRequestNum int, d...

FILE: common/redis.go
  function InitRedisClient (line 17) | func InitRedisClient() (err error) {
  function ParseRedisOption (line 55) | func ParseRedisOption() *redis.Options {
  function RedisSet (line 63) | func RedisSet(key string, value string, expiration time.Duration) error {
  function RedisGet (line 68) | func RedisGet(key string) (string, error) {
  function RedisDel (line 73) | func RedisDel(key string) error {
  function RedisDecrease (line 78) | func RedisDecrease(key string, value int64) error {

FILE: common/render/render.go
  function StringData (line 12) | func StringData(c *gin.Context, str string) {
  function ObjectData (line 19) | func ObjectData(c *gin.Context, object interface{}) error {
  function Done (line 28) | func Done(c *gin.Context) {

FILE: common/utils.go
  function LogQuota (line 8) | func LogQuota(quota int64) string {

FILE: common/utils/array.go
  function DeDuplication (line 3) | func DeDuplication(slice []string) []string {

FILE: common/validate.go
  function init (line 7) | func init() {

FILE: common/verification.go
  type verificationValue (line 10) | type verificationValue struct
  constant EmailVerificationPurpose (line 16) | EmailVerificationPurpose = "v"
  constant PasswordResetPurpose (line 17) | PasswordResetPurpose     = "r"
  function GenerateVerificationCode (line 25) | func GenerateVerificationCode(length int) string {
  function RegisterVerificationCodeWithKey (line 34) | func RegisterVerificationCodeWithKey(key string, code string, purpose st...
  function VerifyCodeWithKey (line 46) | func VerifyCodeWithKey(key string, code string, purpose string) bool {
  function DeleteKey (line 57) | func DeleteKey(key string, purpose string) {
  function removeExpiredPairs (line 64) | func removeExpiredPairs() {
  function init (line 73) | func init() {

FILE: controller/auth/github.go
  type GitHubOAuthResponse (line 22) | type GitHubOAuthResponse struct
  type GitHubUser (line 28) | type GitHubUser struct
  function getGitHubUserInfoByCode (line 34) | func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
  function GitHubOAuth (line 85) | func GitHubOAuth(c *gin.Context) {
  function GitHubBind (line 168) | func GitHubBind(c *gin.Context) {
  function GenerateOAuthCode (line 223) | func GenerateOAuthCode(c *gin.Context) {

FILE: controller/auth/lark.go
  type LarkOAuthResponse (line 21) | type LarkOAuthResponse struct
  type LarkUser (line 25) | type LarkUser struct
  function getLarkUserInfoByCode (line 30) | func getLarkUserInfoByCode(code string) (*LarkUser, error) {
  function LarkOAuth (line 83) | func LarkOAuth(c *gin.Context) {
  function LarkBind (line 157) | func LarkBind(c *gin.Context) {

FILE: controller/auth/oidc.go
  type OidcResponse (line 21) | type OidcResponse struct
  type OidcUser (line 30) | type OidcUser struct
  function getOidcUserInfoByCode (line 38) | func getOidcUserInfoByCode(code string) (*OidcUser, error) {
  function OidcAuth (line 91) | func OidcAuth(c *gin.Context) {
  function OidcBind (line 175) | func OidcBind(c *gin.Context) {

FILE: controller/auth/wechat.go
  type wechatLoginResponse (line 19) | type wechatLoginResponse struct
  function getWeChatIdByCode (line 25) | func getWeChatIdByCode(code string) (string, error) {
  function WeChatAuth (line 56) | func WeChatAuth(c *gin.Context) {
  function WeChatBind (line 119) | func WeChatBind(c *gin.Context) {

FILE: controller/billing.go
  function GetSubscription (line 11) | func GetSubscription(c *gin.Context) {
  function GetUsage (line 65) | func GetUsage(c *gin.Context) {

FILE: controller/channel-billing.go
  type OpenAISubscriptionResponse (line 24) | type OpenAISubscriptionResponse struct
  type OpenAIUsageDailyCost (line 33) | type OpenAIUsageDailyCost struct
  type OpenAICreditGrants (line 41) | type OpenAICreditGrants struct
  type OpenAIUsageResponse (line 48) | type OpenAIUsageResponse struct
  type OpenAISBUsageResponse (line 54) | type OpenAISBUsageResponse struct
  type AIProxyUserOverviewResponse (line 61) | type AIProxyUserOverviewResponse struct
  type API2GPTUsageResponse (line 70) | type API2GPTUsageResponse struct
  type APGC2DGPTUsageResponse (line 77) | type APGC2DGPTUsageResponse struct
  type SiliconFlowUsageResponse (line 85) | type SiliconFlowUsageResponse struct
  type DeepSeekUsageResponse (line 105) | type DeepSeekUsageResponse struct
  type OpenRouterResponse (line 115) | type OpenRouterResponse struct
  function GetAuthHeader (line 123) | func GetAuthHeader(token string) http.Header {
  function GetResponseBody (line 129) | func GetResponseBody(method, url string, channel *model.Channel, headers...
  function updateChannelCloseAIBalance (line 155) | func updateChannelCloseAIBalance(channel *model.Channel) (float64, error) {
  function updateChannelOpenAISBBalance (line 171) | func updateChannelOpenAISBBalance(channel *model.Channel) (float64, erro...
  function updateChannelAIProxyBalance (line 193) | func updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {
  function updateChannelAPI2GPTBalance (line 213) | func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
  function updateChannelAIGC2DBalance (line 229) | func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
  function updateChannelSiliconFlowBalance (line 244) | func updateChannelSiliconFlowBalance(channel *model.Channel) (float64, e...
  function updateChannelDeepSeekBalance (line 266) | func updateChannelDeepSeekBalance(channel *model.Channel) (float64, erro...
  function updateChannelOpenRouterBalance (line 295) | func updateChannelOpenRouterBalance(channel *model.Channel) (float64, er...
  function updateChannelBalance (line 311) | func updateChannelBalance(channel *model.Channel) (float64, error) {
  function UpdateChannelBalance (line 376) | func UpdateChannelBalance(c *gin.Context) {
  function updateAllChannelsBalance (line 409) | func updateAllChannelsBalance() error {
  function UpdateAllChannelsBalance (line 436) | func UpdateAllChannelsBalance(c *gin.Context) {
  function AutomaticallyUpdateChannels (line 452) | func AutomaticallyUpdateChannels(frequency int) {

FILE: controller/channel-test.go
  function buildTestRequest (line 37) | func buildTestRequest(model string) *relaymodel.GeneralOpenAIRequest {
  function parseTestResponse (line 52) | func parseTestResponse(resp string) (*openai.TextResponse, string, error) {
  function testChannel (line 68) | func testChannel(ctx context.Context, channel *model.Channel, request *r...
  function TestChannel (line 169) | func TestChannel(c *gin.Context) {
  function testChannels (line 219) | func testChannels(ctx context.Context, notify bool, scope string) error {
  function TestChannels (line 276) | func TestChannels(c *gin.Context) {
  function AutomaticallyTestChannels (line 297) | func AutomaticallyTestChannels(frequency int) {

FILE: controller/channel.go
  function GetAllChannels (line 13) | func GetAllChannels(c *gin.Context) {
  function SearchChannels (line 34) | func SearchChannels(c *gin.Context) {
  function GetChannel (line 52) | func GetChannel(c *gin.Context) {
  function AddChannel (line 77) | func AddChannel(c *gin.Context) {
  function DeleteChannel (line 113) | func DeleteChannel(c *gin.Context) {
  function DeleteDisabledChannel (line 131) | func DeleteDisabledChannel(c *gin.Context) {
  function UpdateChannel (line 148) | func UpdateChannel(c *gin.Context) {

FILE: controller/group.go
  function GetGroups (line 9) | func GetGroups(c *gin.Context) {

FILE: controller/log.go
  function GetAllLogs (line 12) | func GetAllLogs(c *gin.Context) {
  function GetUserLogs (line 40) | func GetUserLogs(c *gin.Context) {
  function SearchAllLogs (line 67) | func SearchAllLogs(c *gin.Context) {
  function SearchUserLogs (line 85) | func SearchUserLogs(c *gin.Context) {
  function GetLogsStat (line 104) | func GetLogsStat(c *gin.Context) {
  function GetLogsSelfStat (line 125) | func GetLogsSelfStat(c *gin.Context) {
  function DeleteHistoryLogs (line 146) | func DeleteHistoryLogs(c *gin.Context) {

FILE: controller/misc.go
  function GetStatus (line 18) | func GetStatus(c *gin.Context) {
  function GetNotice (line 52) | func GetNotice(c *gin.Context) {
  function GetAbout (line 63) | func GetAbout(c *gin.Context) {
  function GetHomePageContent (line 74) | func GetHomePageContent(c *gin.Context) {
  function SendEmailVerification (line 85) | func SendEmailVerification(c *gin.Context) {
  function SendPasswordResetEmail (line 145) | func SendPasswordResetEmail(c *gin.Context) {
  type PasswordResetRequest (line 194) | type PasswordResetRequest struct
  function ResetPassword (line 199) | func ResetPassword(c *gin.Context) {

FILE: controller/model.go
  type OpenAIModelPermission (line 20) | type OpenAIModelPermission struct
  type OpenAIModels (line 35) | type OpenAIModels struct
  function init (line 49) | func init() {
  function DashboardListModels (line 117) | func DashboardListModels(c *gin.Context) {
  function ListAllModels (line 125) | func ListAllModels(c *gin.Context) {
  function ListModels (line 132) | func ListModels(c *gin.Context) {
  function RetrieveModel (line 171) | func RetrieveModel(c *gin.Context) {
  function GetUserAvailableModels (line 188) | func GetUserAvailableModels(c *gin.Context) {

FILE: controller/option.go
  function GetOptions (line 16) | func GetOptions(c *gin.Context) {
  function UpdateOption (line 37) | func UpdateOption(c *gin.Context) {

FILE: controller/redemption.go
  function GetAllRedemptions (line 14) | func GetAllRedemptions(c *gin.Context) {
  function SearchRedemptions (line 35) | func SearchRedemptions(c *gin.Context) {
  function GetRedemption (line 53) | func GetRedemption(c *gin.Context) {
  function AddRedemption (line 78) | func AddRedemption(c *gin.Context) {
  function DeleteRedemption (line 138) | func DeleteRedemption(c *gin.Context) {
  function UpdateRedemption (line 155) | func UpdateRedemption(c *gin.Context) {

FILE: controller/relay.go
  function relayHelper (line 26) | func relayHelper(c *gin.Context, relayMode int) *model.ErrorWithStatusCo...
  function Relay (line 45) | func Relay(c *gin.Context) {
  function shouldRetry (line 105) | func shouldRetry(c *gin.Context, statusCode int) bool {
  function processChannelRelayError (line 124) | func processChannelRelayError(ctx context.Context, userId int, channelId...
  function RelayNotImplemented (line 134) | func RelayNotImplemented(c *gin.Context) {
  function RelayNotFound (line 146) | func RelayNotFound(c *gin.Context) {

FILE: controller/token.go
  function GetAllTokens (line 16) | func GetAllTokens(c *gin.Context) {
  function SearchTokens (line 41) | func SearchTokens(c *gin.Context) {
  function GetToken (line 60) | func GetToken(c *gin.Context) {
  function GetTokenStatus (line 86) | func GetTokenStatus(c *gin.Context) {
  function validateToken (line 110) | func validateToken(c *gin.Context, token model.Token) error {
  function AddToken (line 123) | func AddToken(c *gin.Context) {
  function DeleteToken (line 170) | func DeleteToken(c *gin.Context) {
  function UpdateToken (line 188) | func UpdateToken(c *gin.Context) {

FILE: controller/user.go
  type LoginRequest (line 21) | type LoginRequest struct
  function Login (line 26) | func Login(c *gin.Context) {
  function SetupLogin (line 68) | func SetupLogin(user *model.User, c *gin.Context) {
  function Logout (line 96) | func Logout(c *gin.Context) {
  function Register (line 113) | func Register(c *gin.Context) {
  function GetAllUsers (line 187) | func GetAllUsers(c *gin.Context) {
  function SearchUsers (line 211) | func SearchUsers(c *gin.Context) {
  function GetUser (line 229) | func GetUser(c *gin.Context) {
  function GetUserDashboard (line 262) | func GetUserDashboard(c *gin.Context) {
  function GenerateAccessToken (line 285) | func GenerateAccessToken(c *gin.Context) {
  function GetAffCode (line 321) | func GetAffCode(c *gin.Context) {
  function GetSelf (line 349) | func GetSelf(c *gin.Context) {
  function UpdateUser (line 367) | func UpdateUser(c *gin.Context) {
  function UpdateSelf (line 432) | func UpdateSelf(c *gin.Context) {
  function DeleteUser (line 479) | func DeleteUser(c *gin.Context) {
  function DeleteSelf (line 514) | func DeleteSelf(c *gin.Context) {
  function CreateUser (line 541) | func CreateUser(c *gin.Context) {
  type ManageRequest (line 591) | type ManageRequest struct
  function ManageUser (line 597) | func ManageUser(c *gin.Context) {
  function EmailBind (line 708) | func EmailBind(c *gin.Context) {
  type topUpRequest (line 750) | type topUpRequest struct
  function TopUp (line 754) | func TopUp(c *gin.Context) {
  type adminTopUpRequest (line 782) | type adminTopUpRequest struct
  function AdminTopUp (line 788) | func AdminTopUp(c *gin.Context) {

FILE: main.go
  function main (line 29) | func main() {

FILE: middleware/auth.go
  function authHelper (line 15) | func authHelper(c *gin.Context, minRole int) {
  function UserAuth (line 73) | func UserAuth() func(c *gin.Context) {
  function AdminAuth (line 79) | func AdminAuth() func(c *gin.Context) {
  function RootAuth (line 85) | func RootAuth() func(c *gin.Context) {
  function TokenAuth (line 91) | func TokenAuth() func(c *gin.Context) {
  function shouldCheckModel (line 153) | func shouldCheckModel(c *gin.Context) bool {

FILE: middleware/cache.go
  function Cache (line 7) | func Cache() func(c *gin.Context) {

FILE: middleware/cors.go
  function CORS (line 8) | func CORS() gin.HandlerFunc {

FILE: middleware/distributor.go
  type ModelRequest (line 16) | type ModelRequest struct
  function Distribute (line 20) | func Distribute() func(c *gin.Context) {
  function SetupContextForSelectedChannel (line 64) | func SetupContextForSelectedChannel(c *gin.Context, channel *model.Chann...

FILE: middleware/gzip.go
  function GzipDecodeMiddleware (line 10) | func GzipDecodeMiddleware() gin.HandlerFunc {

FILE: middleware/language.go
  function Language (line 11) | func Language() gin.HandlerFunc {

FILE: middleware/logger.go
  function SetUpLogger (line 9) | func SetUpLogger(server *gin.Engine) {

FILE: middleware/rate-limit.go
  function redisRateLimiter (line 19) | func redisRateLimiter(c *gin.Context, maxRequestNum int, duration int64,...
  function memoryRateLimiter (line 65) | func memoryRateLimiter(c *gin.Context, maxRequestNum int, duration int64...
  function rateLimitFactory (line 74) | func rateLimitFactory(maxRequestNum int, duration int64, mark string) fu...
  function GlobalWebRateLimit (line 93) | func GlobalWebRateLimit() func(c *gin.Context) {
  function GlobalAPIRateLimit (line 97) | func GlobalAPIRateLimit() func(c *gin.Context) {
  function CriticalRateLimit (line 101) | func CriticalRateLimit() func(c *gin.Context) {
  function DownloadRateLimit (line 105) | func DownloadRateLimit() func(c *gin.Context) {
  function UploadRateLimit (line 109) | func UploadRateLimit() func(c *gin.Context) {

FILE: middleware/recover.go
  function RelayPanicRecover (line 12) | func RelayPanicRecover() gin.HandlerFunc {

FILE: middleware/request-id.go
  function RequestId (line 9) | func RequestId() func(c *gin.Context) {

FILE: middleware/turnstile-check.go
  type turnstileCheckResponse (line 13) | type turnstileCheckResponse struct
  function TurnstileCheck (line 17) | func TurnstileCheck() gin.HandlerFunc {

FILE: middleware/utils.go
  function abortWithMessage (line 12) | func abortWithMessage(c *gin.Context, statusCode int, message string) {
  function getRequestModel (line 23) | func getRequestModel(c *gin.Context) (string, error) {
  function isModelInList (line 52) | func isModelInList(modelName string, models string) bool {

FILE: model/ability.go
  type Ability (line 14) | type Ability struct
  function GetRandomSatisfiedChannel (line 22) | func GetRandomSatisfiedChannel(group string, model string, ignoreFirstPr...
  method AddAbilities (line 53) | func (channel *Channel) AddAbilities() error {
  method DeleteAbilities (line 73) | func (channel *Channel) DeleteAbilities() error {
  method UpdateAbilities (line 79) | func (channel *Channel) UpdateAbilities() error {
  function UpdateAbilityStatus (line 94) | func UpdateAbilityStatus(channelId int, status bool) error {
  function GetGroupModels (line 98) | func GetGroupModels(ctx context.Context, group string) ([]string, error) {

FILE: model/cache.go
  function CacheGetTokenByKey (line 28) | func CacheGetTokenByKey(key string) (*Token, error) {
  function CacheGetUserGroup (line 58) | func CacheGetUserGroup(id int) (group string, err error) {
  function fetchAndUpdateUserQuota (line 76) | func fetchAndUpdateUserQuota(ctx context.Context, id int) (quota int64, ...
  function CacheGetUserQuota (line 88) | func CacheGetUserQuota(ctx context.Context, id int) (quota int64, err er...
  function CacheUpdateUserQuota (line 107) | func CacheUpdateUserQuota(ctx context.Context, id int) error {
  function CacheDecreaseUserQuota (line 119) | func CacheDecreaseUserQuota(id int, quota int64) error {
  function CacheIsUserEnabled (line 127) | func CacheIsUserEnabled(userId int) (bool, error) {
  function CacheGetGroupModels (line 151) | func CacheGetGroupModels(ctx context.Context, group string) ([]string, e...
  function InitChannelCache (line 173) | func InitChannelCache() {
  function SyncChannelCache (line 219) | func SyncChannelCache(frequency int) {
  function CacheGetRandomSatisfiedChannel (line 227) | func CacheGetRandomSatisfiedChannel(group string, model string, ignoreFi...

FILE: model/channel.go
  constant ChannelStatusUnknown (line 14) | ChannelStatusUnknown          = 0
  constant ChannelStatusEnabled (line 15) | ChannelStatusEnabled          = 1
  constant ChannelStatusManuallyDisabled (line 16) | ChannelStatusManuallyDisabled = 2
  constant ChannelStatusAutoDisabled (line 17) | ChannelStatusAutoDisabled     = 3
  type Channel (line 20) | type Channel struct
    method GetPriority (line 100) | func (channel *Channel) GetPriority() int64 {
    method GetBaseURL (line 107) | func (channel *Channel) GetBaseURL() string {
    method GetModelMapping (line 114) | func (channel *Channel) GetModelMapping() map[string]string {
    method Insert (line 127) | func (channel *Channel) Insert() error {
    method Update (line 137) | func (channel *Channel) Update() error {
    method UpdateResponseTime (line 148) | func (channel *Channel) UpdateResponseTime(responseTime int64) {
    method UpdateBalance (line 158) | func (channel *Channel) UpdateBalance(balance float64) {
    method Delete (line 168) | func (channel *Channel) Delete() error {
    method LoadConfig (line 178) | func (channel *Channel) LoadConfig() (ChannelConfig, error) {
  type ChannelConfig (line 43) | type ChannelConfig struct
  function GetAllChannels (line 55) | func GetAllChannels(startIdx int, num int, scope string) ([]*Channel, er...
  function SearchChannels (line 69) | func SearchChannels(keyword string) (channels []*Channel, err error) {
  function GetChannelById (line 74) | func GetChannelById(id int, selectAll bool) (*Channel, error) {
  function BatchInsertChannels (line 85) | func BatchInsertChannels(channels []Channel) error {
  function UpdateChannelStatusById (line 190) | func UpdateChannelStatusById(id int, status int) {
  function UpdateChannelUsedQuota (line 201) | func UpdateChannelUsedQuota(id int, quota int64) {
  function updateChannelUsedQuota (line 209) | func updateChannelUsedQuota(id int, quota int64) {
  function DeleteChannelByStatus (line 216) | func DeleteChannelByStatus(status int64) (int64, error) {
  function DeleteDisabledChannel (line 221) | func DeleteDisabledChannel() (int64, error) {

FILE: model/log.go
  type Log (line 15) | type Log struct
  constant LogTypeUnknown (line 35) | LogTypeUnknown = iota
  constant LogTypeTopup (line 36) | LogTypeTopup
  constant LogTypeConsume (line 37) | LogTypeConsume
  constant LogTypeManage (line 38) | LogTypeManage
  constant LogTypeSystem (line 39) | LogTypeSystem
  constant LogTypeTest (line 40) | LogTypeTest
  function recordLogHelper (line 43) | func recordLogHelper(ctx context.Context, log *Log) {
  function RecordLog (line 54) | func RecordLog(ctx context.Context, userId int, logType int, content str...
  function RecordTopupLog (line 68) | func RecordTopupLog(ctx context.Context, userId int, content string, quo...
  function RecordConsumeLog (line 80) | func RecordConsumeLog(ctx context.Context, log *Log) {
  function RecordTestLog (line 90) | func RecordTestLog(ctx context.Context, log *Log) {
  function GetAllLogs (line 96) | func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, m...
  function GetUserLogs (line 125) | func GetUserLogs(userId int, logType int, startTimestamp int64, endTimes...
  function SearchAllLogs (line 148) | func SearchAllLogs(keyword string) (logs []*Log, err error) {
  function SearchUserLogs (line 153) | func SearchUserLogs(userId int, keyword string) (logs []*Log, err error) {
  function SumUsedQuota (line 158) | func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64,...
  function SumUsedToken (line 186) | func SumUsedToken(logType int, startTimestamp int64, endTimestamp int64,...
  function DeleteOldLog (line 211) | func DeleteOldLog(targetTimestamp int64) (int64, error) {
  type LogStatistic (line 216) | type LogStatistic struct
  function SearchLogsByDayAndModel (line 225) | func SearchLogsByDayAndModel(userId, start, end int) (LogStatistics []*L...

FILE: model/main.go
  function CreateRootAccountIfNeed (line 24) | func CreateRootAccountIfNeed() error {
  function chooseDB (line 67) | func chooseDB(envName string) (*gorm.DB, error) {
  function openPostgreSQL (line 83) | func openPostgreSQL(dsn string) (*gorm.DB, error) {
  function openMySQL (line 94) | func openMySQL(dsn string) (*gorm.DB, error) {
  function openSQLite (line 102) | func openSQLite() (*gorm.DB, error) {
  function InitDB (line 111) | func InitDB() {
  function migrateDB (line 137) | func migrateDB() error {
  function InitLogDB (line 166) | func InitLogDB() {
  function migrateLOGDB (line 195) | func migrateLOGDB() error {
  function setDBConns (line 203) | func setDBConns(db *gorm.DB) *sql.DB {
  function closeDB (line 220) | func closeDB(db *gorm.DB) error {
  function CloseDB (line 229) | func CloseDB() error {

FILE: model/option.go
  type Option (line 12) | type Option struct
  function AllOption (line 17) | func AllOption() ([]*Option, error) {
  function InitOptionMap (line 24) | func InitOptionMap() {
  function loadOptionsFromDatabase (line 82) | func loadOptionsFromDatabase() {
  function SyncOptions (line 95) | func SyncOptions(frequency int) {
  function UpdateOption (line 103) | func UpdateOption(key string, value string) error {
  function updateOptionMap (line 119) | func updateOptionMap(key string, value string) (err error) {

FILE: model/redemption.go
  constant RedemptionCodeStatusEnabled (line 15) | RedemptionCodeStatusEnabled  = 1
  constant RedemptionCodeStatusDisabled (line 16) | RedemptionCodeStatusDisabled = 2
  constant RedemptionCodeStatusUsed (line 17) | RedemptionCodeStatusUsed     = 3
  type Redemption (line 20) | type Redemption struct
    method Insert (line 92) | func (redemption *Redemption) Insert() error {
    method SelectUpdate (line 98) | func (redemption *Redemption) SelectUpdate() error {
    method Update (line 104) | func (redemption *Redemption) Update() error {
    method Delete (line 110) | func (redemption *Redemption) Delete() error {
  function GetAllRedemptions (line 32) | func GetAllRedemptions(startIdx int, num int) ([]*Redemption, error) {
  function SearchRedemptions (line 39) | func SearchRedemptions(keyword string) (redemptions []*Redemption, err e...
  function GetRedemptionById (line 44) | func GetRedemptionById(id int) (*Redemption, error) {
  function Redeem (line 54) | func Redeem(ctx context.Context, key string, userId int) (quota int64, e...
  function DeleteRedemptionById (line 116) | func DeleteRedemptionById(id int) (err error) {

FILE: model/token.go
  constant TokenStatusEnabled (line 17) | TokenStatusEnabled   = 1
  constant TokenStatusDisabled (line 18) | TokenStatusDisabled  = 2
  constant TokenStatusExpired (line 19) | TokenStatusExpired   = 3
  constant TokenStatusExhausted (line 20) | TokenStatusExhausted = 4
  type Token (line 23) | type Token struct
    method Insert (line 126) | func (t *Token) Insert() error {
    method Update (line 133) | func (t *Token) Update() error {
    method SelectUpdate (line 139) | func (t *Token) SelectUpdate() error {
    method Delete (line 144) | func (t *Token) Delete() error {
    method GetModels (line 150) | func (t *Token) GetModels() string {
  function GetAllUserTokens (line 39) | func GetAllUserTokens(userId int, startIdx int, num int, order string) (...
  function SearchUserTokens (line 57) | func SearchUserTokens(userId int, keyword string) (tokens []*Token, err ...
  function ValidateUserToken (line 62) | func ValidateUserToken(key string) (token *Token, err error) {
  function GetTokenByIds (line 106) | func GetTokenByIds(id int, userId int) (*Token, error) {
  function GetTokenById (line 116) | func GetTokenById(id int) (*Token, error) {
  function DeleteTokenById (line 160) | func DeleteTokenById(id int, userId int) (err error) {
  function IncreaseTokenQuota (line 173) | func IncreaseTokenQuota(id int, quota int64) (err error) {
  function increaseTokenQuota (line 184) | func increaseTokenQuota(id int, quota int64) (err error) {
  function DecreaseTokenQuota (line 195) | func DecreaseTokenQuota(id int, quota int64) (err error) {
  function decreaseTokenQuota (line 206) | func decreaseTokenQuota(id int, quota int64) (err error) {
  function PreConsumeTokenQuota (line 217) | func PreConsumeTokenQuota(tokenId int, quota int64) (err error) {
  function PostConsumeTokenQuota (line 282) | func PostConsumeTokenQuota(tokenId int, quota int64) (err error) {

FILE: model/user.go
  constant RoleGuestUser (line 20) | RoleGuestUser  = 0
  constant RoleCommonUser (line 21) | RoleCommonUser = 1
  constant RoleAdminUser (line 22) | RoleAdminUser  = 10
  constant RoleRootUser (line 23) | RoleRootUser   = 100
  constant UserStatusEnabled (line 27) | UserStatusEnabled  = 1
  constant UserStatusDisabled (line 28) | UserStatusDisabled = 2
  constant UserStatusDeleted (line 29) | UserStatusDeleted  = 3
  type User (line 34) | type User struct
    method Insert (line 120) | func (user *User) Insert(ctx context.Context, inviterId int) error {
    method Update (line 167) | func (user *User) Update(updatePassword bool) error {
    method Delete (line 184) | func (user *User) Delete() error {
    method ValidateAndFill (line 196) | func (user *User) ValidateAndFill() (err error) {
    method FillUserById (line 220) | func (user *User) FillUserById() error {
    method FillUserByEmail (line 228) | func (user *User) FillUserByEmail() error {
    method FillUserByGitHubId (line 236) | func (user *User) FillUserByGitHubId() error {
    method FillUserByLarkId (line 244) | func (user *User) FillUserByLarkId() error {
    method FillUserByOidcId (line 252) | func (user *User) FillUserByOidcId() error {
    method FillUserByWeChatId (line 260) | func (user *User) FillUserByWeChatId() error {
    method FillUserByUsername (line 268) | func (user *User) FillUserByUsername() error {
  function GetMaxUserId (line 56) | func GetMaxUserId() int {
  function GetAllUsers (line 62) | func GetAllUsers(startIdx int, num int, order string) (users []*User, er...
  function SearchUsers (line 80) | func SearchUsers(keyword string) (users []*User, err error) {
  function GetUserById (line 89) | func GetUserById(id int, selectAll bool) (*User, error) {
  function GetUserIdByAffCode (line 103) | func GetUserIdByAffCode(affCode string) (int, error) {
  function DeleteUserById (line 112) | func DeleteUserById(id int) (err error) {
  function IsEmailAlreadyTaken (line 276) | func IsEmailAlreadyTaken(email string) bool {
  function IsWeChatIdAlreadyTaken (line 280) | func IsWeChatIdAlreadyTaken(wechatId string) bool {
  function IsGitHubIdAlreadyTaken (line 284) | func IsGitHubIdAlreadyTaken(githubId string) bool {
  function IsLarkIdAlreadyTaken (line 288) | func IsLarkIdAlreadyTaken(githubId string) bool {
  function IsOidcIdAlreadyTaken (line 292) | func IsOidcIdAlreadyTaken(oidcId string) bool {
  function IsUsernameAlreadyTaken (line 296) | func IsUsernameAlreadyTaken(username string) bool {
  function ResetUserPasswordByEmail (line 300) | func ResetUserPasswordByEmail(email string, password string) error {
  function IsAdmin (line 312) | func IsAdmin(userId int) bool {
  function IsUserEnabled (line 325) | func IsUserEnabled(userId int) (bool, error) {
  function ValidateAccessToken (line 337) | func ValidateAccessToken(token string) (user *User) {
  function GetUserQuota (line 349) | func GetUserQuota(id int) (quota int64, err error) {
  function GetUserUsedQuota (line 354) | func GetUserUsedQuota(id int) (quota int64, err error) {
  function GetUserEmail (line 359) | func GetUserEmail(id int) (email string, err error) {
  function GetUserGroup (line 364) | func GetUserGroup(id int) (group string, err error) {
  function IncreaseUserQuota (line 374) | func IncreaseUserQuota(id int, quota int64) (err error) {
  function increaseUserQuota (line 385) | func increaseUserQuota(id int, quota int64) (err error) {
  function DecreaseUserQuota (line 390) | func DecreaseUserQuota(id int, quota int64) (err error) {
  function decreaseUserQuota (line 401) | func decreaseUserQuota(id int, quota int64) (err error) {
  function GetRootUserEmail (line 406) | func GetRootUserEmail() (email string) {
  function UpdateUserUsedQuotaAndRequestCount (line 411) | func UpdateUserUsedQuotaAndRequestCount(id int, quota int64) {
  function updateUserUsedQuotaAndRequestCount (line 420) | func updateUserUsedQuotaAndRequestCount(id int, quota int64, count int) {
  function updateUserUsedQuota (line 432) | func updateUserUsedQuota(id int, quota int64) {
  function updateUserRequestCount (line 443) | func updateUserRequestCount(id int, count int) {
  function GetUsernameById (line 450) | func GetUsernameById(id int) (username string) {

FILE: model/utils.go
  constant BatchUpdateTypeUserQuota (line 11) | BatchUpdateTypeUserQuota = iota
  constant BatchUpdateTypeTokenQuota (line 12) | BatchUpdateTypeTokenQuota
  constant BatchUpdateTypeUsedQuota (line 13) | BatchUpdateTypeUsedQuota
  constant BatchUpdateTypeChannelUsedQuota (line 14) | BatchUpdateTypeChannelUsedQuota
  constant BatchUpdateTypeRequestCount (line 15) | BatchUpdateTypeRequestCount
  constant BatchUpdateTypeCount (line 16) | BatchUpdateTypeCount
  function init (line 22) | func init() {
  function InitBatchUpdater (line 29) | func InitBatchUpdater() {
  function addNewRecord (line 38) | func addNewRecord(type_ int, id int, value int64) {
  function batchUpdate (line 48) | func batchUpdate() {

FILE: monitor/channel.go
  function notifyRootUser (line 12) | func notifyRootUser(subject string, content string) {
  function DisableChannel (line 31) | func DisableChannel(channelId int, channelName string, reason string) {
  function MetricDisableChannel (line 47) | func MetricDisableChannel(channelId int, successRate float64) {
  function EnableChannel (line 64) | func EnableChannel(channelId int, channelName string) {

FILE: monitor/manage.go
  function ShouldDisableChannel (line 11) | func ShouldDisableChannel(err *model.Error, statusCode int) bool {
  function ShouldEnableChannel (line 46) | func ShouldEnableChannel(err error, openAIErr *model.Error) bool {

FILE: monitor/metric.go
  function consumeSuccess (line 11) | func consumeSuccess(channelId int) {
  function consumeFail (line 18) | func consumeFail(channelId int) (bool, float64) {
  function metricSuccessConsumer (line 40) | func metricSuccessConsumer() {
  function metricFailConsumer (line 49) | func metricFailConsumer() {
  function init (line 61) | func init() {
  function Emit (line 68) | func Emit(channelId int, success bool) {

FILE: relay/adaptor.go
  function GetAdaptor (line 27) | func GetAdaptor(apiType int) adaptor.Adaptor {

FILE: relay/adaptor/aiproxy/adaptor.go
  type Adaptor (line 14) | type Adaptor struct
    method Init (line 18) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 22) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 26) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 32) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 41) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 48) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 52) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 61) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 65) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/aiproxy/constants.go
  function init (line 7) | func init() {

FILE: relay/adaptor/aiproxy/main.go
  function ConvertRequest (line 25) | func ConvertRequest(request model.GeneralOpenAIRequest) *LibraryRequest {
  function aiProxyDocuments2Markdown (line 37) | func aiProxyDocuments2Markdown(documents []LibraryDocument) string {
  function responseAIProxyLibrary2OpenAI (line 48) | func responseAIProxyLibrary2OpenAI(response *LibraryResponse) *openai.Te...
  function documentsAIProxyLibrary (line 67) | func documentsAIProxyLibrary(documents []LibraryDocument) *openai.ChatCo...
  function streamResponseAIProxyLibrary2OpenAI (line 80) | func streamResponseAIProxyLibrary2OpenAI(response *LibraryStreamResponse...
  function StreamHandler (line 92) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 153) | func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatu...

FILE: relay/adaptor/aiproxy/model.go
  type LibraryRequest (line 3) | type LibraryRequest struct
  type LibraryError (line 10) | type LibraryError struct
  type LibraryDocument (line 15) | type LibraryDocument struct
  type LibraryResponse (line 20) | type LibraryResponse struct
  type LibraryStreamResponse (line 27) | type LibraryStreamResponse struct

FILE: relay/adaptor/ali/adaptor.go
  type Adaptor (line 17) | type Adaptor struct
    method Init (line 21) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 25) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 39) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 56) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 70) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 79) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 83) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 99) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 103) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/ali/image.go
  function ImageHandler (line 19) | func ImageHandler(c *gin.Context, resp *http.Response) (*model.ErrorWith...
  function asyncTask (line 71) | func asyncTask(taskID string, key string) (*TaskResponse, error, []byte) {
  function asyncTaskWait (line 103) | func asyncTaskWait(taskID string, key string) (*TaskResponse, []byte, er...
  function responseAli2OpenAIImage (line 142) | func responseAli2OpenAIImage(response *TaskResponse, responseFormat stri...
  function getImageData (line 174) | func getImageData(url string) ([]byte, error) {
  function Base64Encode (line 189) | func Base64Encode(data []byte) string {

FILE: relay/adaptor/ali/main.go
  constant EnableSearchModelSuffix (line 22) | EnableSearchModelSuffix = "-internet"
  function ConvertRequest (line 24) | func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
  function ConvertEmbeddingRequest (line 59) | func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *Embedd...
  function ConvertImageRequest (line 70) | func ConvertImageRequest(request model.ImageRequest) *ImageRequest {
  function EmbeddingHandler (line 81) | func EmbeddingHandler(c *gin.Context, resp *http.Response) (*model.Error...
  function embeddingResponseAli2OpenAI (line 117) | func embeddingResponseAli2OpenAI(response *EmbeddingResponse) *openai.Em...
  function responseAli2OpenAI (line 135) | func responseAli2OpenAI(response *ChatResponse) *openai.TextResponse {
  function streamResponseAli2OpenAI (line 150) | func streamResponseAli2OpenAI(aliResponse *ChatResponse) *openai.ChatCom...
  function StreamHandler (line 171) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 230) | func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatu...

FILE: relay/adaptor/ali/model.go
  type Message (line 8) | type Message struct
  type Input (line 13) | type Input struct
  type Parameters (line 18) | type Parameters struct
  type ChatRequest (line 30) | type ChatRequest struct
  type ImageRequest (line 36) | type ImageRequest struct
  type TaskResponse (line 51) | type TaskResponse struct
  type Header (line 76) | type Header struct
  type Payload (line 86) | type Payload struct
  type WSSMessage (line 104) | type WSSMessage struct
  type EmbeddingRequest (line 109) | type EmbeddingRequest struct
  type Embedding (line 119) | type Embedding struct
  type EmbeddingResponse (line 124) | type EmbeddingResponse struct
  type Error (line 132) | type Error struct
  type Usage (line 138) | type Usage struct
  type Output (line 144) | type Output struct
  type ChatResponse (line 150) | type ChatResponse struct

FILE: relay/adaptor/alibailian/main.go
  function GetRequestURL (line 10) | func GetRequestURL(meta *meta.Meta) (string, error) {

FILE: relay/adaptor/anthropic/adaptor.go
  type Adaptor (line 16) | type Adaptor struct
    method Init (line 19) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 23) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 27) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 46) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 53) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 60) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 64) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 73) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 77) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/anthropic/main.go
  function stopReasonClaude2OpenAI (line 21) | func stopReasonClaude2OpenAI(reason *string) string {
  function ConvertRequest (line 39) | func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
  function StreamResponseClaude2OpenAI (line 149) | func StreamResponseClaude2OpenAI(claudeResponse *StreamResponse) (*opena...
  function ResponseClaude2OpenAI (line 210) | func ResponseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse {
  function StreamHandler (line 249) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 338) | func Handler(c *gin.Context, resp *http.Response, promptTokens int, mode...

FILE: relay/adaptor/anthropic/model.go
  type Metadata (line 5) | type Metadata struct
  type ImageSource (line 9) | type ImageSource struct
  type Content (line 15) | type Content struct
  type Message (line 27) | type Message struct
  type Tool (line 32) | type Tool struct
  type InputSchema (line 38) | type InputSchema struct
  type Request (line 44) | type Request struct
  type Usage (line 59) | type Usage struct
  type Error (line 64) | type Error struct
  type Response (line 69) | type Response struct
  type Delta (line 81) | type Delta struct
  type StreamResponse (line 89) | type StreamResponse struct

FILE: relay/adaptor/aws/adaptor.go
  type Adaptor (line 20) | type Adaptor struct
    method Init (line 27) | func (a *Adaptor) Init(meta *meta.Meta) {
    method ConvertRequest (line 35) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoResponse (line 49) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 56) | func (a *Adaptor) GetModelList() (models []string) {
    method GetChannelName (line 63) | func (a *Adaptor) GetChannelName() string {
    method GetRequestURL (line 67) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 71) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertImageRequest (line 75) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 82) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...

FILE: relay/adaptor/aws/claude/adapter.go
  type Adaptor (line 16) | type Adaptor struct
    method ConvertRequest (line 19) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoResponse (line 30) | func (a *Adaptor) DoResponse(c *gin.Context, awsCli *bedrockruntime.Cl...

FILE: relay/adaptor/aws/claude/main.go
  function awsModelID (line 41) | func awsModelID(requestModel string) (string, error) {
  function Handler (line 49) | func Handler(c *gin.Context, awsCli *bedrockruntime.Client, modelName st...
  function StreamHandler (line 102) | func StreamHandler(c *gin.Context, awsCli *bedrockruntime.Client) (*rela...

FILE: relay/adaptor/aws/claude/model.go
  type Request (line 8) | type Request struct

FILE: relay/adaptor/aws/llama3/adapter.go
  type Adaptor (line 16) | type Adaptor struct
    method ConvertRequest (line 19) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoResponse (line 30) | func (a *Adaptor) DoResponse(c *gin.Context, awsCli *bedrockruntime.Cl...

FILE: relay/adaptor/aws/llama3/main.go
  function awsModelID (line 35) | func awsModelID(requestModel string) (string, error) {
  constant promptTemplate (line 44) | promptTemplate = `<|begin_of_text|>{{range .Messages}}<|start_header_id|...
  function RenderPrompt (line 49) | func RenderPrompt(messages []relaymodel.Message) string {
  function ConvertRequest (line 58) | func ConvertRequest(textRequest relaymodel.GeneralOpenAIRequest) *Request {
  function Handler (line 72) | func Handler(c *gin.Context, awsCli *bedrockruntime.Client, modelName st...
  function ResponseLlama2OpenAI (line 118) | func ResponseLlama2OpenAI(llamaResponse *Response) *openai.TextResponse {
  function StreamHandler (line 141) | func StreamHandler(c *gin.Context, awsCli *bedrockruntime.Client) (*rela...
  function StreamResponseLlama2OpenAI (line 219) | func StreamResponseLlama2OpenAI(llamaResponse *StreamResponse) *openai.C...

FILE: relay/adaptor/aws/llama3/main_test.go
  function TestRenderPrompt (line 11) | func TestRenderPrompt(t *testing.T) {

FILE: relay/adaptor/aws/llama3/model.go
  type Request (line 6) | type Request struct
  type Response (line 16) | type Response struct
  type StreamResponse (line 24) | type StreamResponse struct

FILE: relay/adaptor/aws/registry.go
  type AwsModelType (line 9) | type AwsModelType
  constant AwsClaude (line 12) | AwsClaude AwsModelType = iota + 1
  constant AwsLlama3 (line 13) | AwsLlama3
  function init (line 20) | func init() {
  function GetAdaptor (line 29) | func GetAdaptor(model string) utils.AwsAdapter {

FILE: relay/adaptor/aws/utils/adaptor.go
  type AwsAdapter (line 16) | type AwsAdapter interface
  type Adaptor (line 21) | type Adaptor struct
    method Init (line 26) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 34) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 38) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertImageRequest (line 42) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 49) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...

FILE: relay/adaptor/aws/utils/utils.go
  function WrapErr (line 9) | func WrapErr(err error) *relaymodel.ErrorWithStatusCode {

FILE: relay/adaptor/baidu/adaptor.go
  type Adaptor (line 17) | type Adaptor struct
    method Init (line 20) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 24) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 92) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 98) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 112) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 119) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 123) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 137) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 141) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/baidu/main.go
  type TokenResponse (line 26) | type TokenResponse struct
  type Message (line 31) | type Message struct
  type ChatRequest (line 36) | type ChatRequest struct
  type Error (line 49) | type Error struct
  function ConvertRequest (line 56) | func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
  function responseBaidu2OpenAI (line 81) | func responseBaidu2OpenAI(response *ChatResponse) *openai.TextResponse {
  function streamResponseBaidu2OpenAI (line 100) | func streamResponseBaidu2OpenAI(baiduResponse *ChatStreamResponse) *open...
  function ConvertEmbeddingRequest (line 116) | func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *Embedd...
  function embeddingResponseBaidu2OpenAI (line 122) | func embeddingResponseBaidu2OpenAI(response *EmbeddingResponse) *openai....
  function StreamHandler (line 139) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 184) | func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatu...
  function EmbeddingHandler (line 221) | func EmbeddingHandler(c *gin.Context, resp *http.Response) (*model.Error...
  function GetAccessToken (line 257) | func GetAccessToken(apiKey string) (string, error) {
  function getBaiduAccessTokenHelper (line 280) | func getBaiduAccessTokenHelper(apiKey string) (*AccessToken, error) {

FILE: relay/adaptor/baidu/model.go
  type ChatResponse (line 8) | type ChatResponse struct
  type ChatStreamResponse (line 19) | type ChatStreamResponse struct
  type EmbeddingRequest (line 25) | type EmbeddingRequest struct
  type EmbeddingData (line 29) | type EmbeddingData struct
  type EmbeddingResponse (line 35) | type EmbeddingResponse struct
  type AccessToken (line 44) | type AccessToken struct

FILE: relay/adaptor/baiduv2/main.go
  function GetRequestURL (line 10) | func GetRequestURL(meta *meta.Meta) (string, error) {

FILE: relay/adaptor/cloudflare/adaptor.go
  type Adaptor (line 17) | type Adaptor struct
    method ConvertImageRequest (line 22) | func (*Adaptor) ConvertImageRequest(request *model.ImageRequest) (any,...
    method Init (line 28) | func (a *Adaptor) Init(meta *meta.Meta) {
    method isAIGateWay (line 35) | func (a *Adaptor) isAIGateWay(baseURL string) bool {
    method GetRequestURL (line 39) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 61) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 67) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoRequest (line 81) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 85) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 94) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 98) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/cloudflare/main.go
  function ConvertCompletionsRequest (line 21) | func ConvertCompletionsRequest(textRequest model.GeneralOpenAIRequest) *...
  function StreamHandler (line 31) | func StreamHandler(c *gin.Context, resp *http.Response, promptTokens int...
  function Handler (line 85) | func Handler(c *gin.Context, resp *http.Response, promptTokens int, mode...

FILE: relay/adaptor/cloudflare/model.go
  type Request (line 5) | type Request struct

FILE: relay/adaptor/cohere/adaptor.go
  type Adaptor (line 15) | type Adaptor struct
    method ConvertImageRequest (line 18) | func (*Adaptor) ConvertImageRequest(request *model.ImageRequest) (any,...
    method Init (line 24) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 28) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 32) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 38) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoRequest (line 45) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 49) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 58) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 62) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/cohere/constant.go
  function init (line 9) | func init() {

FILE: relay/adaptor/cohere/main.go
  function stopReasonCohere2OpenAI (line 24) | func stopReasonCohere2OpenAI(reason *string) string {
  function ConvertRequest (line 36) | func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
  function StreamResponseCohere2OpenAI (line 77) | func StreamResponseCohere2OpenAI(cohereResponse *StreamResponse) (*opena...
  function ResponseCohere2OpenAI (line 114) | func ResponseCohere2OpenAI(cohereResponse *Response) *openai.TextResponse {
  function StreamHandler (line 134) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 187) | func Handler(c *gin.Context, resp *http.Response, promptTokens int, mode...

FILE: relay/adaptor/cohere/model.go
  type Request (line 3) | type Request struct
  type ChatMessage (line 26) | type ChatMessage struct
  type Tool (line 31) | type Tool struct
  type ParameterSpec (line 37) | type ParameterSpec struct
  type ToolResult (line 43) | type ToolResult struct
  type ToolCall (line 48) | type ToolCall struct
  type StreamResponse (line 53) | type StreamResponse struct
  type SearchQuery (line 66) | type SearchQuery struct
  type SearchResult (line 71) | type SearchResult struct
  type Connector (line 77) | type Connector struct
  type Document (line 81) | type Document struct
  type Citation (line 89) | type Citation struct
  type Response (line 96) | type Response struct
  type Message (line 110) | type Message struct
  type Version (line 115) | type Version struct
  type Units (line 119) | type Units struct
  type ChatEntry (line 124) | type ChatEntry struct
  type Meta (line 129) | type Meta struct
  type APIVersion (line 135) | type APIVersion struct
  type BilledUnits (line 139) | type BilledUnits struct
  type Usage (line 144) | type Usage struct

FILE: relay/adaptor/common.go
  function SetupCommonRequestHeader (line 13) | func SetupCommonRequestHeader(c *gin.Context, req *http.Request, meta *m...
  function DoRequestHelper (line 21) | func DoRequestHelper(a Adaptor, c *gin.Context, meta *meta.Meta, request...
  function DoRequest (line 41) | func DoRequest(c *gin.Context, req *http.Request) (*http.Response, error) {

FILE: relay/adaptor/coze/adaptor.go
  type Adaptor (line 15) | type Adaptor struct
    method Init (line 19) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 23) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 27) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 33) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 41) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 48) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 52) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 69) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 73) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/coze/constant/contenttype/define.go
  constant Text (line 4) | Text = "text"

FILE: relay/adaptor/coze/constant/event/define.go
  constant Message (line 4) | Message = "message"
  constant Done (line 5) | Done    = "done"
  constant Error (line 6) | Error   = "error"

FILE: relay/adaptor/coze/constant/messagetype/define.go
  constant Answer (line 4) | Answer   = "answer"
  constant FollowUp (line 5) | FollowUp = "follow_up"

FILE: relay/adaptor/coze/helper.go
  function event2StopReason (line 5) | func event2StopReason(e *string) string {

FILE: relay/adaptor/coze/main.go
  function stopReasonCoze2OpenAI (line 24) | func stopReasonCoze2OpenAI(reason *string) string {
  function ConvertRequest (line 40) | func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
  function StreamResponseCoze2OpenAI (line 60) | func StreamResponseCoze2OpenAI(cozeResponse *StreamResponse) (*openai.Ch...
  function ResponseCoze2OpenAI (line 83) | func ResponseCoze2OpenAI(cozeResponse *Response) *openai.TextResponse {
  function StreamHandler (line 110) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 165) | func Handler(c *gin.Context, resp *http.Response, promptTokens int, mode...

FILE: relay/adaptor/coze/model.go
  type Message (line 3) | type Message struct
  type ErrorInformation (line 10) | type ErrorInformation struct
  type Request (line 15) | type Request struct
  type Response (line 24) | type Response struct
  type StreamResponse (line 31) | type StreamResponse struct

FILE: relay/adaptor/deepl/adaptor.go
  type Adaptor (line 14) | type Adaptor struct
    method Init (line 19) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 23) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 27) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 33) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 42) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 49) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 53) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 67) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 71) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/deepl/helper.go
  function parseLangFromModelName (line 5) | func parseLangFromModelName(modelName string) string {

FILE: relay/adaptor/deepl/main.go
  function ConvertRequest (line 19) | func ConvertRequest(textRequest model.GeneralOpenAIRequest) (*Request, s...
  function StreamResponseDeepL2OpenAI (line 31) | func StreamResponseDeepL2OpenAI(deeplResponse *Response) *openai.ChatCom...
  function ResponseDeepL2OpenAI (line 46) | func ResponseDeepL2OpenAI(deeplResponse *Response) *openai.TextResponse {
  function StreamHandler (line 68) | func StreamHandler(c *gin.Context, resp *http.Response, modelName string...
  function Handler (line 103) | func Handler(c *gin.Context, resp *http.Response, modelName string) *mod...

FILE: relay/adaptor/deepl/model.go
  type Request (line 3) | type Request struct
  type Translation (line 8) | type Translation struct
  type Response (line 13) | type Response struct

FILE: relay/adaptor/doubao/main.go
  function GetRequestURL (line 9) | func GetRequestURL(meta *meta.Meta) (string, error) {

FILE: relay/adaptor/gemini/adaptor.go
  type Adaptor (line 20) | type Adaptor struct
    method Init (line 23) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 26) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 49) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 55) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 69) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 76) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 80) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 96) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 100) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/gemini/constants.go
  function IsModelSupportSystemInstruction (line 27) | func IsModelSupportSystemInstruction(model string) bool {

FILE: relay/adaptor/gemini/main.go
  constant VisionMaxImageNum (line 29) | VisionMaxImageNum = 16
  function ConvertRequest (line 38) | func ConvertRequest(textRequest model.GeneralOpenAIRequest) *ChatRequest {
  function ConvertEmbeddingRequest (line 164) | func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *BatchE...
  type ChatResponse (line 187) | type ChatResponse struct
    method GetResponseText (line 192) | func (g *ChatResponse) GetResponseText() string {
  type ChatCandidate (line 202) | type ChatCandidate struct
  type ChatSafetyRating (line 209) | type ChatSafetyRating struct
  type ChatPromptFeedback (line 214) | type ChatPromptFeedback struct
  function getToolCalls (line 218) | func getToolCalls(candidate *ChatCandidate) []model.Tool {
  function responseGeminiChat2OpenAI (line 242) | func responseGeminiChat2OpenAI(response *ChatResponse) *openai.TextRespo...
  function streamResponseGeminiChat2OpenAI (line 279) | func streamResponseGeminiChat2OpenAI(geminiResponse *ChatResponse) *open...
  function embeddingResponseGemini2OpenAI (line 292) | func embeddingResponseGemini2OpenAI(response *EmbeddingResponse) *openai...
  function StreamHandler (line 309) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 359) | func Handler(c *gin.Context, resp *http.Response, promptTokens int, mode...
  function EmbeddingHandler (line 403) | func EmbeddingHandler(c *gin.Context, resp *http.Response) (*model.Error...

FILE: relay/adaptor/gemini/model.go
  type ChatRequest (line 3) | type ChatRequest struct
  type EmbeddingRequest (line 11) | type EmbeddingRequest struct
  type BatchEmbeddingRequest (line 19) | type BatchEmbeddingRequest struct
  type EmbeddingData (line 23) | type EmbeddingData struct
  type EmbeddingResponse (line 27) | type EmbeddingResponse struct
  type Error (line 32) | type Error struct
  type InlineData (line 38) | type InlineData struct
  type FunctionCall (line 43) | type FunctionCall struct
  type Part (line 48) | type Part struct
  type ChatContent (line 54) | type ChatContent struct
  type ChatSafetySettings (line 59) | type ChatSafetySettings struct
  type ChatTools (line 64) | type ChatTools struct
  type ChatGenerationConfig (line 68) | type ChatGenerationConfig struct

FILE: relay/adaptor/geminiv2/main.go
  function GetRequestURL (line 10) | func GetRequestURL(meta *meta.Meta) (string, error) {

FILE: relay/adaptor/interface.go
  type Adaptor (line 11) | type Adaptor interface

FILE: relay/adaptor/minimax/main.go
  function GetRequestURL (line 9) | func GetRequestURL(meta *meta.Meta) (string, error) {

FILE: relay/adaptor/novita/main.go
  function GetRequestURL (line 10) | func GetRequestURL(meta *meta.Meta) (string, error) {

FILE: relay/adaptor/ollama/adaptor.go
  type Adaptor (line 16) | type Adaptor struct
    method Init (line 19) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 23) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 32) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 38) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 51) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 58) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 62) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 76) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 80) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/ollama/main.go
  function ConvertRequest (line 25) | func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
  function responseOllama2OpenAI (line 61) | func responseOllama2OpenAI(response *ChatResponse) *openai.TextResponse {
  function streamResponseOllama2OpenAI (line 87) | func streamResponseOllama2OpenAI(ollamaResponse *ChatResponse) *openai.C...
  function StreamHandler (line 104) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function ConvertEmbeddingRequest (line 162) | func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *Embedd...
  function EmbeddingHandler (line 176) | func EmbeddingHandler(c *gin.Context, resp *http.Response) (*model.Error...
  function embeddingResponseOllama2OpenAI (line 211) | func embeddingResponseOllama2OpenAI(response *EmbeddingResponse) *openai...
  function Handler (line 229) | func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatu...

FILE: relay/adaptor/ollama/model.go
  type Options (line 3) | type Options struct
  type Message (line 14) | type Message struct
  type ChatRequest (line 20) | type ChatRequest struct
  type ChatResponse (line 27) | type ChatResponse struct
  type EmbeddingRequest (line 41) | type EmbeddingRequest struct
  type EmbeddingResponse (line 49) | type EmbeddingResponse struct

FILE: relay/adaptor/openai/adaptor.go
  type Adaptor (line 25) | type Adaptor struct
    method Init (line 29) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 33) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 70) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 84) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 98) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 105) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 109) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 131) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 136) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/openai/compatible.go
  function GetCompatibleChannelMeta (line 46) | func GetCompatibleChannelMeta(channelType int) (string, []string) {

FILE: relay/adaptor/openai/helper.go
  function ResponseText2Usage (line 11) | func ResponseText2Usage(responseText string, modelName string, promptTok...
  function GetFullRequestURL (line 19) | func GetFullRequestURL(baseURL string, requestURL string, channelType in...

FILE: relay/adaptor/openai/image.go
  function ImageHandler (line 12) | func ImageHandler(c *gin.Context, resp *http.Response) (*model.ErrorWith...

FILE: relay/adaptor/openai/main.go
  constant dataPrefix (line 22) | dataPrefix       = "data: "
  constant done (line 23) | done             = "[DONE]"
  constant dataPrefixLength (line 24) | dataPrefixLength = len(dataPrefix)
  function StreamHandler (line 27) | func StreamHandler(c *gin.Context, resp *http.Response, relayMode int) (...
  function Handler (line 99) | func Handler(c *gin.Context, resp *http.Response, promptTokens int, mode...

FILE: relay/adaptor/openai/model.go
  type TextContent (line 5) | type TextContent struct
  type ImageContent (line 10) | type ImageContent struct
  type ChatRequest (line 15) | type ChatRequest struct
  type TextRequest (line 21) | type TextRequest struct
  type ImageRequest (line 30) | type ImageRequest struct
  type WhisperJSONResponse (line 41) | type WhisperJSONResponse struct
  type WhisperVerboseJSONResponse (line 45) | type WhisperVerboseJSONResponse struct
  type Segment (line 53) | type Segment struct
  type TextToSpeechRequest (line 66) | type TextToSpeechRequest struct
  type UsageOrResponseText (line 74) | type UsageOrResponseText struct
  type SlimTextResponse (line 79) | type SlimTextResponse struct
  type TextResponseChoice (line 85) | type TextResponseChoice struct
  type TextResponse (line 91) | type TextResponse struct
  type EmbeddingResponseItem (line 100) | type EmbeddingResponseItem struct
  type EmbeddingResponse (line 106) | type EmbeddingResponse struct
  type ImageData (line 113) | type ImageData struct
  type ImageResponse (line 119) | type ImageResponse struct
  type ChatCompletionsStreamResponseChoice (line 125) | type ChatCompletionsStreamResponseChoice struct
  type ChatCompletionsStreamResponse (line 131) | type ChatCompletionsStreamResponse struct
  type CompletionsStreamResponse (line 140) | type CompletionsStreamResponse struct

FILE: relay/adaptor/openai/token.go
  function InitTokenEncoders (line 22) | func InitTokenEncoders() {
  function getTokenEncoder (line 52) | func getTokenEncoder(model string) *tiktoken.Tiktoken {
  function getTokenNum (line 69) | func getTokenNum(tokenEncoder *tiktoken.Tiktoken, text string) int {
  function CountTokenMessages (line 76) | func CountTokenMessages(messages []model.Message, model string) int {
  constant lowDetailCost (line 137) | lowDetailCost         = 85
  constant highDetailCostPerTile (line 138) | highDetailCostPerTile = 170
  constant additionalCost (line 139) | additionalCost        = 85
  constant gpt4oMiniLowDetailCost (line 141) | gpt4oMiniLowDetailCost  = 2833
  constant gpt4oMiniHighDetailCost (line 142) | gpt4oMiniHighDetailCost = 5667
  constant gpt4oMiniAdditionalCost (line 143) | gpt4oMiniAdditionalCost = 2833
  function countImageTokens (line 148) | func countImageTokens(url string, detail string, model string) (_ int, e...
  function CountTokenInput (line 214) | func CountTokenInput(input any, model string) int {
  function CountTokenText (line 228) | func CountTokenText(text string, model string) int {
  function CountToken (line 233) | func CountToken(text string) int {

FILE: relay/adaptor/openai/util.go
  function ErrorWrapper (line 11) | func ErrorWrapper(err error, code string, statusCode int) *model.ErrorWi...

FILE: relay/adaptor/palm/adaptor.go
  type Adaptor (line 15) | type Adaptor struct
    method Init (line 18) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 22) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 26) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 32) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 39) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 46) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 50) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 61) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 65) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/palm/model.go
  type ChatMessage (line 7) | type ChatMessage struct
  type Filter (line 12) | type Filter struct
  type Prompt (line 17) | type Prompt struct
  type ChatRequest (line 21) | type ChatRequest struct
  type Error (line 29) | type Error struct
  type ChatResponse (line 35) | type ChatResponse struct

FILE: relay/adaptor/palm/palm.go
  function ConvertRequest (line 23) | func ConvertRequest(textRequest model.GeneralOpenAIRequest) *ChatRequest {
  function responsePaLM2OpenAI (line 47) | func responsePaLM2OpenAI(response *ChatResponse) *openai.TextResponse {
  function streamResponsePaLM2OpenAI (line 65) | func streamResponsePaLM2OpenAI(palmResponse *ChatResponse) *openai.ChatC...
  function StreamHandler (line 78) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 130) | func Handler(c *gin.Context, resp *http.Response, promptTokens int, mode...

FILE: relay/adaptor/proxy/adaptor.go
  constant channelName (line 20) | channelName = "proxy"
  type Adaptor (line 22) | type Adaptor struct
    method Init (line 24) | func (a *Adaptor) Init(meta *meta.Meta) {
    method ConvertRequest (line 27) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoResponse (line 31) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 51) | func (a *Adaptor) GetModelList() (models []string) {
    method GetChannelName (line 55) | func (a *Adaptor) GetChannelName() string {
    method GetRequestURL (line 60) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 66) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertImageRequest (line 83) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 87) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...

FILE: relay/adaptor/replicate/adaptor.go
  type Adaptor (line 21) | type Adaptor struct
    method ConvertImageRequest (line 26) | func (*Adaptor) ConvertImageRequest(request *model.ImageRequest) (any,...
    method ConvertRequest (line 42) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method Init (line 94) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 98) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 106) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method DoRequest (line 112) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 117) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 130) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 134) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/replicate/chat.go
  function ChatHandler (line 20) | func ChatHandler(c *gin.Context, resp *http.Response) (
  constant eventPrefix (line 111) | eventPrefix = "event: "
  constant dataPrefix (line 112) | dataPrefix  = "data: "
  constant done (line 113) | done        = "[DONE]"
  function chatStreamHandler (line 116) | func chatStreamHandler(c *gin.Context, streamUrl string) (responseText s...

FILE: relay/adaptor/replicate/image.go
  function ImageHandler (line 44) | func ImageHandler(c *gin.Context, resp *http.Response) (*model.ErrorWith...
  function ConvertImageToPNG (line 188) | func ConvertImageToPNG(webpData []byte) ([]byte, error) {

FILE: relay/adaptor/replicate/model.go
  type DrawImageRequest (line 12) | type DrawImageRequest struct
  type ImageInput (line 19) | type ImageInput struct
  type InpaintingImageByFlusReplicateRequest (line 36) | type InpaintingImageByFlusReplicateRequest struct
  type FluxInpaintingInput (line 43) | type FluxInpaintingInput struct
  type ImageResponse (line 58) | type ImageResponse struct
    method GetOutput (line 75) | func (r *ImageResponse) GetOutput() ([]string, error) {
  type FluxMetrics (line 101) | type FluxMetrics struct
  type FluxURLs (line 108) | type FluxURLs struct
  type ReplicateChatRequest (line 113) | type ReplicateChatRequest struct
  type ChatInput (line 120) | type ChatInput struct
  type ChatResponse (line 137) | type ChatResponse struct
  type ChatResponseUrl (line 155) | type ChatResponseUrl struct

FILE: relay/adaptor/tencent/adaptor.go
  type Adaptor (line 22) | type Adaptor struct
    method Init (line 29) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 35) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 39) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 48) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 72) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 79) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 83) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 99) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 103) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/tencent/main.go
  function ConvertRequest (line 31) | func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
  function ConvertEmbeddingRequest (line 49) | func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *Embedd...
  function EmbeddingHandler (line 55) | func EmbeddingHandler(c *gin.Context, resp *http.Response) (*model.Error...
  function embeddingResponseTencent2OpenAI (line 90) | func embeddingResponseTencent2OpenAI(response *EmbeddingResponse) *opena...
  function responseTencent2OpenAI (line 108) | func responseTencent2OpenAI(response *ChatResponse) *openai.TextResponse {
  function streamResponseTencent2OpenAI (line 133) | func streamResponseTencent2OpenAI(TencentResponse *ChatResponse) *openai...
  function StreamHandler (line 151) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 197) | func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatu...
  function ParseConfig (line 237) | func ParseConfig(config string) (appId int64, secretId string, secretKey...
  function sha256hex (line 249) | func sha256hex(s string) string {
  function hmacSha256 (line 254) | func hmacSha256(s, key string) string {
  function GetSign (line 260) | func GetSign(req any, adaptor *Adaptor, secId, secKey string) string {

FILE: relay/adaptor/tencent/model.go
  type Message (line 3) | type Message struct
  type ChatRequest (line 8) | type ChatRequest struct
  type Error (line 46) | type Error struct
  type Usage (line 51) | type Usage struct
  type ResponseChoices (line 57) | type ResponseChoices struct
  type ChatResponse (line 63) | type ChatResponse struct
  type ChatResponseP (line 73) | type ChatResponseP struct
  type EmbeddingRequest (line 77) | type EmbeddingRequest struct
  type EmbeddingData (line 81) | type EmbeddingData struct
  type EmbeddingUsage (line 87) | type EmbeddingUsage struct
  type EmbeddingResponse (line 92) | type EmbeddingResponse struct
  type EmbeddingResponseP (line 99) | type EmbeddingResponseP struct

FILE: relay/adaptor/vertexai/adaptor.go
  constant channelName (line 20) | channelName = "vertexai"
  type Adaptor (line 22) | type Adaptor struct
    method Init (line 24) | func (a *Adaptor) Init(meta *meta.Meta) {
    method ConvertRequest (line 27) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoResponse (line 40) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 53) | func (a *Adaptor) GetModelList() (models []string) {
    method GetChannelName (line 58) | func (a *Adaptor) GetChannelName() string {
    method GetRequestURL (line 62) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 98) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertImageRequest (line 108) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 115) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...

FILE: relay/adaptor/vertexai/claude/adapter.go
  constant anthropicVersion (line 24) | anthropicVersion = "vertex-2023-10-16"
  type Adaptor (line 26) | type Adaptor struct
    method ConvertRequest (line 29) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoResponse (line 53) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...

FILE: relay/adaptor/vertexai/claude/model.go
  type Request (line 5) | type Request struct

FILE: relay/adaptor/vertexai/gemini/adapter.go
  type Adaptor (line 27) | type Adaptor struct
    method ConvertRequest (line 30) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method DoResponse (line 41) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...

FILE: relay/adaptor/vertexai/registry.go
  type VertexAIModelType (line 13) | type VertexAIModelType
  constant VerterAIClaude (line 16) | VerterAIClaude VertexAIModelType = iota + 1
  constant VerterAIGemini (line 17) | VerterAIGemini
  function init (line 23) | func init() {
  type innerAIAdapter (line 35) | type innerAIAdapter interface
  function GetAdaptor (line 40) | func GetAdaptor(model string) innerAIAdapter {

FILE: relay/adaptor/vertexai/token.go
  type ApplicationDefaultCredentials (line 15) | type ApplicationDefaultCredentials struct
  constant defaultScope (line 31) | defaultScope = "https://www.googleapis.com/auth/cloud-platform"
  function getToken (line 33) | func getToken(ctx context.Context, channelId int, adcJson string) (strin...

FILE: relay/adaptor/xunfei/adaptor.go
  type Adaptor (line 15) | type Adaptor struct
    method Init (line 20) | func (a *Adaptor) Init(meta *meta.Meta) {
    method GetRequestURL (line 24) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 28) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 34) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 42) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 49) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponse (line 56) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 80) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 84) | func (a *Adaptor) GetChannelName() string {

FILE: relay/adaptor/xunfei/domain.go
  function parseAPIVersionByModelName (line 31) | func parseAPIVersionByModelName(modelName string) string {
  function modelName2APIVersion (line 44) | func modelName2APIVersion(modelName string) string {
  function apiVersion2domain (line 63) | func apiVersion2domain(apiVersion string) string {
  function getXunfeiAuthUrl (line 83) | func getXunfeiAuthUrl(apiVersion string, apiKey string, apiSecret string...

FILE: relay/adaptor/xunfei/main.go
  function requestOpenAI2Xunfei (line 32) | func requestOpenAI2Xunfei(request model.GeneralOpenAIRequest, xunfeiAppI...
  function getToolCalls (line 61) | func getToolCalls(response *ChatResponse) []model.Tool {
  function responseXunfei2OpenAI (line 79) | func responseXunfei2OpenAI(response *ChatResponse) *openai.TextResponse {
  function streamResponseXunfei2OpenAI (line 106) | func streamResponseXunfei2OpenAI(xunfeiResponse *ChatResponse) *openai.C...
  function buildXunfeiAuthUrl (line 130) | func buildXunfeiAuthUrl(hostUrl string, apiKey, apiSecret string) string {
  function StreamHandler (line 156) | func StreamHandler(c *gin.Context, meta *meta.Meta, textRequest model.Ge...
  function Handler (line 186) | func Handler(c *gin.Context, meta *meta.Meta, textRequest model.GeneralO...
  function xunfeiMakeRequest (line 224) | func xunfeiMakeRequest(textRequest model.GeneralOpenAIRequest, domain, a...

FILE: relay/adaptor/xunfei/model.go
  type Message (line 7) | type Message struct
  type Functions (line 12) | type Functions struct
  type ChatRequest (line 16) | type ChatRequest struct
  type ChatResponseTextItem (line 37) | type ChatResponseTextItem struct
  type ChatResponse (line 45) | type ChatResponse struct

FILE: relay/adaptor/zhipu/adaptor.go
  type Adaptor (line 18) | type Adaptor struct
    method Init (line 22) | func (a *Adaptor) Init(meta *meta.Meta) {
    method SetVersionByModeName (line 26) | func (a *Adaptor) SetVersionByModeName(modelName string) {
    method GetRequestURL (line 34) | func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
    method SetupRequestHeader (line 52) | func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request...
    method ConvertRequest (line 59) | func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, reques...
    method ConvertImageRequest (line 83) | func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (an...
    method DoRequest (line 95) | func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBo...
    method DoResponseV4 (line 99) | func (a *Adaptor) DoResponseV4(c *gin.Context, resp *http.Response, me...
    method DoResponse (line 108) | func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta...
    method GetModelList (line 143) | func (a *Adaptor) GetModelList() []string {
    method GetChannelName (line 147) | func (a *Adaptor) GetChannelName() string {
  function ConvertEmbeddingRequest (line 132) | func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) (*Embed...

FILE: relay/adaptor/zhipu/main.go
  function GetToken (line 31) | func GetToken(apikey string) string {
  function ConvertRequest (line 78) | func ConvertRequest(request model.GeneralOpenAIRequest) *Request {
  function responseZhipu2OpenAI (line 94) | func responseZhipu2OpenAI(response *Response) *openai.TextResponse {
  function streamResponseZhipu2OpenAI (line 119) | func streamResponseZhipu2OpenAI(zhipuResponse string) *openai.ChatComple...
  function streamMetaResponseZhipu2OpenAI (line 131) | func streamMetaResponseZhipu2OpenAI(zhipuResponse *StreamMetaResponse) (...
  function StreamHandler (line 145) | func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWit...
  function Handler (line 212) | func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatu...
  function EmbeddingsHandler (line 249) | func EmbeddingsHandler(c *gin.Context, resp *http.Response) (*model.Erro...
  function embeddingResponseZhipu2OpenAI (line 274) | func embeddingResponseZhipu2OpenAI(response *EmbeddingResponse) *openai....

FILE: relay/adaptor/zhipu/model.go
  type Message (line 8) | type Message struct
  type Request (line 13) | type Request struct
  type ResponseData (line 21) | type ResponseData struct
  type Response (line 29) | type Response struct
  type StreamMetaResponse (line 36) | type StreamMetaResponse struct
  type tokenData (line 43) | type tokenData struct
  type EmbeddingRequest (line 48) | type EmbeddingRequest struct
  type EmbeddingResponse (line 53) | type EmbeddingResponse struct
  type EmbeddingData (line 60) | type EmbeddingData struct
  type ImageRequest (line 66) | type ImageRequest struct

FILE: relay/adaptor_test.go
  function TestGetAdaptor (line 9) | func TestGetAdaptor(t *testing.T) {

FILE: relay/apitype/define.go
  constant OpenAI (line 4) | OpenAI = iota
  constant Anthropic (line 5) | Anthropic
  constant PaLM (line 6) | PaLM
  constant Baidu (line 7) | Baidu
  constant Zhipu (line 8) | Zhipu
  constant Ali (line 9) | Ali
  constant Xunfei (line 10) | Xunfei
  constant AIProxyLibrary (line 11) | AIProxyLibrary
  constant Tencent (line 12) | Tencent
  constant Gemini (line 13) | Gemini
  constant Ollama (line 14) | Ollama
  constant AwsClaude (line 15) | AwsClaude
  constant Coze (line 16) | Coze
  constant Cohere (line 17) | Cohere
  constant Cloudflare (line 18) | Cloudflare
  constant DeepL (line 19) | DeepL
  constant VertexAI (line 20) | VertexAI
  constant Proxy (line 21) | Proxy
  constant Replicate (line 22) | Replicate
  constant Dummy (line 24) | Dummy

FILE: relay/billing/billing.go
  function ReturnPreConsumedQuota (line 11) | func ReturnPreConsumedQuota(ctx context.Context, preConsumedQuota int64,...
  function PostConsumeQuota (line 23) | func PostConsumeQuota(ctx context.Context, tokenId int, quotaDelta int64...

FILE: relay/billing/ratio/group.go
  function GroupRatio2JSONString (line 16) | func GroupRatio2JSONString() string {
  function UpdateGroupRatioByJSONString (line 24) | func UpdateGroupRatioByJSONString(jsonStr string) error {
  function GetGroupRatio (line 31) | func GetGroupRatio(name string) float64 {

FILE: relay/billing/ratio/model.go
  constant USD2RMB (line 13) | USD2RMB   = 7
  constant USD (line 14) | USD       = 500
  constant MILLI_USD (line 15) | MILLI_USD = 1.0 / 1000 * USD
  constant RMB (line 16) | RMB       = USD / USD2RMB
  function init (line 640) | func init() {
  function AddNewMissingRatio (line 651) | func AddNewMissingRatio(oldRatio string) string {
  function ModelRatio2JSONString (line 671) | func ModelRatio2JSONString() string {
  function UpdateModelRatioByJSONString (line 679) | func UpdateModelRatioByJSONString(jsonStr string) error {
  function GetModelRatio (line 686) | func GetModelRatio(name string, channelType int) float64 {
  function CompletionRatio2JSONString (line 712) | func CompletionRatio2JSONString() string {
  function UpdateCompletionRatioByJSONString (line 720) | func UpdateCompletionRatioByJSONString(jsonStr string) error {
  function GetCompletionRatio (line 725) | func GetCompletionRatio(name string, channelType int) float64 {

FILE: relay/channeltype/define.go
  constant Unknown (line 4) | Unknown = iota
  constant OpenAI (line 5) | OpenAI
  constant API2D (line 6) | API2D
  constant Azure (line 7) | Azure
  constant CloseAI (line 8) | CloseAI
  constant OpenAISB (line 9) | OpenAISB
  constant OpenAIMax (line 10) | OpenAIMax
  constant OhMyGPT (line 11) | OhMyGPT
  constant Custom (line 12) | Custom
  constant Ails (line 13) | Ails
  constant AIProxy (line 14) | AIProxy
  constant PaLM (line 15) | PaLM
  constant API2GPT (line 16) | API2GPT
  constant AIGC2D (line 17) | AIGC2D
  constant Anthropic (line 18) | Anthropic
  constant Baidu (line 19) | Baidu
  constant Zhipu (line 20) | Zhipu
  constant Ali (line 21) | Ali
  constant Xunfei (line 22) | Xunfei
  constant AI360 (line 23) | AI360
  constant OpenRouter (line 24) | OpenRouter
  constant AIProxyLibrary (line 25) | AIProxyLibrary
  constant FastGPT (line 26) | FastGPT
  constant Tencent (line 27) | Tencent
  constant Gemini (line 28) | Gemini
  constant Moonshot (line 29) | Moonshot
  constant Baichuan (line 30) | Baichuan
  constant Minimax (line 31) | Minimax
  constant Mistral (line 32) | Mistral
  constant Groq (line 33) | Groq
  constant Ollama (line 34) | Ollama
  constant LingYiWanWu (line 35) | LingYiWanWu
  constant StepFun (line 36) | StepFun
  constant AwsClaude (line 37) | AwsClaude
  constant Coze (line 38) | Coze
  constant Cohere (line 39) | Cohere
  constant DeepSeek (line 40) | DeepSeek
  constant Cloudflare (line 41) | Cloudflare
  constant DeepL (line 42) | DeepL
  constant TogetherAI (line 43) | TogetherAI
  constant Doubao (line 44) | Doubao
  constant Novita (line 45) | Novita
  constant VertextAI (line 46) | VertextAI
  constant Proxy (line 47) | Proxy
  constant SiliconFlow (line 48) | SiliconFlow
  constant XAI (line 49) | XAI
  constant Replicate (line 50) | Replicate
  constant BaiduV2 (line 51) | BaiduV2
  constant XunfeiV2 (line 52) | XunfeiV2
  constant AliBailian (line 53) | AliBailian
  constant OpenAICompatible (line 54) | OpenAICompatible
  constant GeminiOpenAICompatible (line 55) | GeminiOpenAICompatible
  constant Dummy (line 56) | Dummy

FILE: relay/channeltype/helper.go
  function ToAPIType (line 5) | func ToAPIType(channelType int) int {

FILE: relay/channeltype/url.go
  function init (line 59) | func init() {

FILE: relay/channeltype/url_test.go
  function TestChannelBaseURLs (line 8) | func TestChannelBaseURLs(t *testing.T) {

FILE: relay/constant/finishreason/define.go
  constant Stop (line 4) | Stop = "stop"

FILE: relay/constant/role/define.go
  constant System (line 4) | System    = "system"
  constant Assistant (line 5) | Assistant = "assistant"

FILE: relay/controller/audio.go
  function RelayAudioHelper (line 30) | func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWi...
  function getTextFromVTT (line 238) | func getTextFromVTT(body []byte) (string, error) {
  function getTextFromVerboseJSON (line 242) | func getTextFromVerboseJSON(body []byte) (string, error) {
  function getTextFromSRT (line 250) | func getTextFromSRT(body []byte) (string, error) {
  function getTextFromText (line 271) | func getTextFromText(body []byte) (string, error) {
  function getTextFromJSON (line 275) | func getTextFromJSON(body []byte) (string, error) {

FILE: relay/controller/error.go
  type GeneralErrorResponse (line 14) | type GeneralErrorResponse struct
    method ToMessage (line 30) | func (e GeneralErrorResponse) ToMessage() string {
  function RelayErrorHandler (line 55) | func RelayErrorHandler(resp *http.Response) (ErrorWithStatusCode *model....

FILE: relay/controller/helper.go
  function getAndValidateTextRequest (line 29) | func getAndValidateTextRequest(c *gin.Context, relayMode int) (*relaymod...
  function getPromptTokens (line 48) | func getPromptTokens(textRequest *relaymodel.GeneralOpenAIRequest, relay...
  function getPreConsumedQuota (line 60) | func getPreConsumedQuota(textRequest *relaymodel.GeneralOpenAIRequest, p...
  function preConsumeQuota (line 68) | func preConsumeQuota(ctx context.Context, textRequest *relaymodel.Genera...
  function postConsumeQuota (line 97) | func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta...
  function getMappedModelName (line 143) | func getMappedModelName(modelName string, mapping map[string]string) (st...
  function isErrorHappened (line 154) | func isErrorHappened(meta *meta.Meta, resp *http.Response) bool {
  function setSystemPrompt (line 180) | func setSystemPrompt(ctx context.Context, request *relaymodel.GeneralOpe...

FILE: relay/controller/image.go
  function getImageRequest (line 26) | func getImageRequest(c *gin.Context, _ int) (*relaymodel.ImageRequest, e...
  function isValidImageSize (line 44) | func isValidImageSize(model string, size string) bool {
  function isValidImagePromptLength (line 52) | func isValidImagePromptLength(model string, promptLength int) bool {
  function isWithinRange (line 57) | func isWithinRange(element string, value int) bool {
  function getImageSizeRatio (line 62) | func getImageSizeRatio(model string, size string) float64 {
  function validateImageRequest (line 69) | func validateImageRequest(imageRequest *relaymodel.ImageRequest, _ *meta...
  function getImageCostRatio (line 91) | func getImageCostRatio(imageRequest *relaymodel.ImageRequest) (float64, ...
  function RelayImageHelper (line 106) | func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWi...

FILE: relay/controller/proxy.go
  function RelayProxyHelper (line 17) | func RelayProxyHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWi...

FILE: relay/controller/text.go
  function RelayTextHelper (line 25) | func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode {
  function getRequestBody (line 90) | func getRequestBody(c *gin.Context, meta *meta.Meta, textRequest *model....

FILE: relay/controller/validator/validation.go
  function ValidateTextRequest (line 10) | func ValidateTextRequest(textRequest *model.GeneralOpenAIRequest, relayM...

FILE: relay/meta/relay_meta.go
  type Meta (line 15) | type Meta struct
  function GetByContext (line 40) | func GetByContext(c *gin.Context) *Meta {

FILE: relay/model/constant.go
  constant ContentTypeText (line 4) | ContentTypeText       = "text"
  constant ContentTypeImageURL (line 5) | ContentTypeImageURL   = "image_url"
  constant ContentTypeInputAudio (line 6) | ContentTypeInputAudio = "input_audio"

FILE: relay/model/general.go
  type ResponseFormat (line 3) | type ResponseFormat struct
  type JSONSchema (line 8) | type JSONSchema struct
  type Audio (line 15) | type Audio struct
  type StreamOptions (line 20) | type StreamOptions struct
  type GeneralOpenAIRequest (line 24) | type GeneralOpenAIRequest struct
    method ParseInput (line 71) | func (r GeneralOpenAIRequest) ParseInput() []string {

FILE: relay/model/image.go
  type ImageRequest (line 3) | type ImageRequest struct

FILE: relay/model/message.go
  type Message (line 3) | type Message struct
    method IsStringContent (line 12) | func (m Message) IsStringContent() bool {
    method StringContent (line 17) | func (m Message) StringContent() string {
    method ParseContent (line 41) | func (m Message) ParseContent() []MessageContent {
  type ImageURL (line 82) | type ImageURL struct
  type MessageContent (line 87) | type MessageContent struct

FILE: relay/model/misc.go
  type Usage (line 3) | type Usage struct
  type CompletionTokensDetails (line 11) | type CompletionTokensDetails struct
  type Error (line 17) | type Error struct
  type ErrorWithStatusCode (line 24) | type ErrorWithStatusCode struct

FILE: relay/model/tool.go
  type Tool (line 3) | type Tool struct
  type Function (line 9) | type Function struct

FILE: relay/relaymode/define.go
  constant Unknown (line 4) | Unknown = iota
  constant ChatCompletions (line 5) | ChatCompletions
  constant Completions (line 6) | Completions
  constant Embeddings (line 7) | Embeddings
  constant Moderations (line 8) | Moderations
  constant ImagesGenerations (line 9) | ImagesGenerations
  constant Edits (line 10) | Edits
  constant AudioSpeech (line 11) | AudioSpeech
  constant AudioTranscription (line 12) | AudioTranscription
  constant AudioTranslation (line 13) | AudioTranslation
  constant Proxy (line 15) | Proxy

FILE: relay/relaymode/helper.go
  function GetByPath (line 5) | func GetByPath(path string) int {

FILE: router/api.go
  function SetApiRouter (line 12) | func SetApiRouter(router *gin.Engine) {

FILE: router/dashboard.go
  function SetDashboardRouter (line 10) | func SetDashboardRouter(router *gin.Engine) {

FILE: router/main.go
  function SetRouter (line 14) | func SetRouter(router *gin.Engine, buildFS embed.FS) {

FILE: router/relay.go
  function SetRelayRouter (line 10) | func SetRelayRouter(router *gin.Engine) {

FILE: router/web.go
  function SetWebRouter (line 17) | func SetWebRouter(router *gin.Engine, buildFS embed.FS) {

FILE: web/air/src/App.js
  function App (line 30) | function App() {

FILE: web/air/src/components/ChannelsTable.js
  function renderTimestamp (line 23) | function renderTimestamp(timestamp) {
  function renderType (line 33) | function renderType(type) {

FILE: web/air/src/components/HeaderBar.js
  function logout (line 45) | async function logout() {

FILE: web/air/src/components/LoginForm.js
  function handleChange (line 73) | function handleChange(name, value) {
  function handleSubmit (line 77) | async function handleSubmit(e) {

FILE: web/air/src/components/LogsTable.js
  function renderTimestamp (line 11) | function renderTimestamp(timestamp) {
  constant MODE_OPTIONS (line 17) | const MODE_OPTIONS = [{ key: 'all', text: '全部用户', value: 'all' }, { key:...
  function renderType (line 21) | function renderType(type) {
  function renderIsStream (line 38) | function renderIsStream(bool) {
  function renderUseTime (line 46) | function renderUseTime(type) {

FILE: web/air/src/components/MjLogsTable.js
  function renderType (line 13) | function renderType(type) {
  function renderCode (line 51) | function renderCode(code) {
  function renderStatus (line 67) | function renderStatus(type) {

FILE: web/air/src/components/PasswordResetConfirm.js
  function handleSubmit (line 43) | async function handleSubmit(e) {

FILE: web/air/src/components/PasswordResetForm.js
  function handleChange (line 32) | function handleChange(e) {
  function handleSubmit (line 37) | async function handleSubmit(e) {

FILE: web/air/src/components/PrivateRoute.js
  function PrivateRoute (line 6) | function PrivateRoute({ children }) {

FILE: web/air/src/components/RedemptionsTable.js
  function renderTimestamp (line 9) | function renderTimestamp(timestamp) {
  function renderStatus (line 17) | function renderStatus(status) {

FILE: web/air/src/components/RegisterForm.js
  function handleChange (line 41) | function handleChange(e) {
  function handleSubmit (line 47) | async function handleSubmit(e) {

FILE: web/air/src/components/TokensTable.js
  constant COPY_OPTIONS (line 11) | const COPY_OPTIONS = [
  constant OPEN_LINK_OPTIONS (line 18) | const OPEN_LINK_OPTIONS = [
  function renderTimestamp (line 24) | function renderTimestamp(timestamp) {
  function renderStatus (line 32) | function renderStatus(status, model_limits_enabled = false) {

FILE: web/air/src/components/UsersTable.js
  function renderRole (line 9) | function renderRole(role) {

FILE: web/air/src/components/WeChatIcon.js
  function CustomIcon (line 5) | function CustomIcon() {

FILE: web/air/src/components/utils.js
  function getOAuthState (line 3) | async function getOAuthState() {
  function onGitHubOAuthClicked (line 14) | async function onGitHubOAuthClicked(github_client_id) {

FILE: web/air/src/constants/channel.constants.js
  constant CHANNEL_OPTIONS (line 1) | const CHANNEL_OPTIONS = [

FILE: web/air/src/constants/common.constant.js
  constant ITEMS_PER_PAGE (line 1) | const ITEMS_PER_PAGE = 10;

FILE: web/air/src/helpers/api.js
  constant API (line 4) | const API = axios.create({

FILE: web/air/src/helpers/auth-header.js
  function authHeader (line 1) | function authHeader() {

FILE: web/air/src/helpers/render.js
  function renderText (line 4) | function renderText(text, limit) {
  function renderGroup (line 11) | function renderGroup(group) {
  function renderNumber (line 33) | function renderNumber(num) {
  function renderQuotaNumberWithDigit (line 45) | function renderQuotaNumberWithDigit(num, digits = 2) {
  function renderNumberWithPoint (line 54) | function renderNumberWithPoint(num) {
  function getQuotaPerUnit (line 82) | function getQuotaPerUnit() {
  function getQuotaWithUnit (line 88) | function getQuotaWithUnit(quota, digits = 6) {
  function renderQuota (line 94) | function renderQuota(quota, digits = 2) {
  function renderQuotaWithPrompt (line 105) | function renderQuotaWithPrompt(quota, digits) {
  function stringToColor (line 160) | function stringToColor(str) {

FILE: web/air/src/helpers/utils.js
  function isAdmin (line 10) | function isAdmin() {
  function isRoot (line 17) | function isRoot() {
  function getSystemName (line 24) | function getSystemName() {
  function getLogo (line 30) | function getLogo() {
  function getFooterHTML (line 36) | function getFooterHTML() {
  function copy (line 40) | async function copy(text) {
  function isMobile (line 51) | function isMobile() {
  function showError (line 75) | function showError(error) {
  function showWarning (line 104) | function showWarning(message) {
  function showSuccess (line 108) | function showSuccess(message) {
  function showInfo (line 112) | function showInfo(message) {
  function showNotice (line 116) | function showNotice(message, isHTML = false) {
  function openPage (line 124) | function openPage(url) {
  function removeTrailingSlash (line 128) | function removeTrailingSlash(url) {
  function timestamp2string (line 136) | function timestamp2string(timestamp) {
  function timestamp2string1 (line 174) | function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
  function downloadTextAsFile (line 207) | function downloadTextAsFile(text, filename) {
  function shouldShowPrompt (line 225) | function shouldShowPrompt(id) {
  function setPromptShown (line 231) | function setPromptShown(id) {

FILE: web/air/src/pages/Channel/EditChannel.js
  constant MODEL_MAPPING_EXAMPLE (line 8) | const MODEL_MAPPING_EXAMPLE = {
  function type2secretPrompt (line 14) | function type2secretPrompt(type) {

FILE: web/berry/src/constants/ChannelConstants.js
  constant CHANNEL_OPTIONS (line 1) | const CHANNEL_OPTIONS = {

FILE: web/berry/src/constants/CommonConstants.js
  constant ITEMS_PER_PAGE (line 1) | const ITEMS_PER_PAGE = 10;

FILE: web/berry/src/routes/index.js
  function ThemeRoutes (line 9) | function ThemeRoutes() {

FILE: web/berry/src/serviceWorker.js
  function registerValidSW (line 21) | function registerValidSW(swUrl, config) {
  function checkValidServiceWorker (line 62) | function checkValidServiceWorker(swUrl, config) {
  function register (line 87) | function register(config) {
  function unregister (line 118) | function unregister() {

FILE: web/berry/src/store/actions.js
  constant SET_MENU (line 2) | const SET_MENU = '@customization/SET_MENU';
  constant MENU_TOGGLE (line 3) | const MENU_TOGGLE = '@customization/MENU_TOGGLE';
  constant MENU_OPEN (line 4) | const MENU_OPEN = '@customization/MENU_OPEN';
  constant SET_FONT_FAMILY (line 5) | const SET_FONT_FAMILY = '@customization/SET_FONT_FAMILY';
  constant SET_BORDER_RADIUS (line 6) | const SET_BORDER_RADIUS = '@customization/SET_BORDER_RADIUS';
  constant SET_SITE_INFO (line 7) | const SET_SITE_INFO = '@siteInfo/SET_SITE_INFO';
  constant LOGIN (line 8) | const LOGIN = '@account/LOGIN';
  constant LOGOUT (line 9) | const LOGOUT = '@account/LOGOUT';
  constant SET_THEME (line 10) | const SET_THEME = '@customization/SET_THEME';

FILE: web/berry/src/themes/compStyleOverride.js
  function componentStyleOverrides (line 1) | function componentStyleOverrides(theme) {

FILE: web/berry/src/themes/index.js
  function GetDarkOption (line 48) | function GetDarkOption() {
  function GetLightOption (line 71) | function GetLightOption() {

FILE: web/berry/src/themes/palette.js
  function themePalette (line 6) | function themePalette(theme) {

FILE: web/berry/src/themes/typography.js
  function themeTypography (line 6) | function themeTypography(theme) {

FILE: web/berry/src/ui-component/TableToolBar.js
  function TableToolBar (line 12) | function TableToolBar({ filterName, handleFilterName, placeholder }) {

FILE: web/berry/src/ui-component/ThemeButton.js
  function ThemeButton (line 7) | function ThemeButton() {

FILE: web/berry/src/ui-component/cards/UserCard.js
  function UserCard (line 41) | function UserCard({ children }) {

FILE: web/berry/src/utils/api.js
  constant API (line 7) | const API = axios.create({

FILE: web/berry/src/utils/chart.js
  function getLastSevenDays (line 1) | function getLastSevenDays() {
  function getTodayDay (line 16) | function getTodayDay() {
  function generateChartOptions (line 21) | function generateChartOptions(data, unit) {

FILE: web/berry/src/utils/common.js
  function getSystemName (line 5) | function getSystemName() {
  function isMobile (line 11) | function isMobile() {
  function SnackbarHTMLContent (line 16) | function SnackbarHTMLContent({htmlContent}) {
  function getSnackbarOptions (line 20) | function getSnackbarOptions(variant) {
  function showError (line 29) | function showError(error) {
  function showNotice (line 52) | function showNotice(message, isHTML = false) {
  function showWarning (line 60) | function showWarning(message) {
  function showSuccess (line 64) | function showSuccess(message) {
  function showInfo (line 68) | function showInfo(message) {
  function getOAuthState (line 72) | async function getOAuthState() {
  function onGitHubOAuthClicked (line 83) | async function onGitHubOAuthClicked(github_client_id, openInNewTab = fal...
  function onLarkOAuthClicked (line 94) | async function onLarkOAuthClicked(lark_client_id) {
  function onOidcClicked (line 101) | async function onOidcClicked(auth_url, client_id, openInNewTab = false) {
  function isAdmin (line 115) | function isAdmin() {
  function timestamp2string (line 122) | function timestamp2string(timestamp) {
  function calculateQuota (line 148) | function calculateQuota(quota, digits = 2) {
  function renderQuota (line 155) | function renderQuota(quota, digits = 2) {
  function renderNumber (line 173) | function renderNumber(num) {
  function renderQuotaWithPrompt (line 185) | function renderQuotaWithPrompt(quota, digits) {
  function downloadTextAsFile (line 194) | function downloadTextAsFile(text, filename) {
  function removeTrailingSlash (line 203) | function removeTrailingSlash(url) {
  function loadChannelModels (line 213) | async function loadChannelModels() {
  function getChannelModels (line 223) | function getChannelModels(type) {
  function copy (line 238) | function copy(text, name = '') {

FILE: web/berry/src/views/Channel/component/EditModal.js
  function initialModel (line 204) | function initialModel(channelModel) {

FILE: web/berry/src/views/Channel/component/GroupLabel.js
  function name2color (line 6) | function name2color(name) {

FILE: web/berry/src/views/Channel/component/TableRow.js
  function ChannelTableRow (line 33) | function ChannelTableRow({
  function renderBalance (line 255) | function renderBalance(type, balance) {

FILE: web/berry/src/views/Channel/index.js
  function ChannelPage (line 26) | function ChannelPage() {

FILE: web/berry/src/views/Dashboard/index.js
  function getLineDataGroup (line 117) | function getLineDataGroup(statisticalData) {
  function getBarDataGroup (line 150) | function getBarDataGroup(data) {
  function getLineCardOption (line 178) | function getLineCardOption(lineDataGroup, field) {

FILE: web/berry/src/views/Error/index.js
  function NotFoundView (line 9) | function NotFoundView() {

FILE: web/berry/src/views/Log/component/TableRow.js
  function renderType (line 9) | function renderType(type) {
  function LogTableRow (line 28) | function LogTableRow({ item, userIsAdmin }) {

FILE: web/berry/src/views/Log/component/TableToolBar.js
  function TableToolBar (line 25) | function TableToolBar({

FILE: web/berry/src/views/Log/index.js
  function Log (line 22) | function Log() {

FILE: web/berry/src/views/Log/type/LogType.js
  constant LOG_TYPE (line 1) | const LOG_TYPE = {

FILE: web/berry/src/views/Profile/index.js
  function Profile (line 41) | function Profile() {

FILE: web/berry/src/views/Redemption/component/TableRow.js
  function RedemptionTableRow (line 25) | function RedemptionTableRow({ item, manageRedemption, handleOpenModal, s...

FILE: web/berry/src/views/Redemption/index.js
  function Redemption (line 23) | function Redemption() {

FILE: web/berry/src/views/Setting/index.js
  function CustomTabPanel (line 11) | function CustomTabPanel(props) {
  function a11yProps (line 27) | function a11yProps(index) {

FILE: web/berry/src/views/Token/component/TableRow.js
  constant COPY_OPTIONS (line 27) | const COPY_OPTIONS = [
  function replacePlaceholders (line 39) | function replacePlaceholders(text, key, serverAddress) {
  function createMenu (line 43) | function createMenu(menuItems) {
  function TokensTableRow (line 56) | function TokensTableRow({ item, manageToken, handleOpenModal, setModalTo...

FILE: web/berry/src/views/Token/index.js
  function Token (line 24) | function Token() {

FILE: web/berry/src/views/User/component/TableRow.js
  function renderRole (line 26) | function renderRole(role) {
  function UsersTableRow (line 39) | function UsersTableRow({ item, manageUser, handleOpenModal, setModalUser...

FILE: web/berry/src/views/User/index.js
  function Users (line 23) | function Users() {

FILE: web/default/src/App.js
  function App (line 33) | function App() {

FILE: web/default/src/components/ChannelsTable.js
  function renderTimestamp (line 19) | function renderTimestamp(timestamp) {
  function renderType (line 25) | function renderType(type, t) {
  function renderBalance (line 44) | function renderBalance(type, balance, t) {
  function isShowDetail (line 74) | function isShowDetail() {

FILE: web/default/src/components/Header.js
  function logout (line 93) | async function logout() {

FILE: web/default/src/components/LoginForm.js
  function handleChange (line 69) | function handleChange(e) {
  function handleSubmit (line 74) | async function handleSubmit(e) {

FILE: web/default/src/components/LogsTable.js
  function renderTimestamp (line 28) | function renderTimestamp(timestamp, request_id) {
  constant MODE_OPTIONS (line 45) | const MODE_OPTIONS = [
  function renderType (line 50) | function renderType(type) {
  function getColorByElapsedTime (line 91) | function getColorByElapsedTime(elapsedTime) {
  function renderDetail (line 100) | function renderDetail(log) {

FILE: web/default/src/components/PasswordResetConfirm.js
  function handleSubmit (line 52) | async function handleSubmit(e) {

FILE: web/default/src/components/PasswordResetForm.js
  function handleChange (line 53) | function handleChange(e) {
  function handleSubmit (line 58) | async function handleSubmit(e) {

FILE: web/default/src/components/PrivateRoute.js
  function PrivateRoute (line 6) | function PrivateRoute({ children }) {

FILE: web/default/src/components/RedemptionsTable.js
  function renderTimestamp (line 25) | function renderTimestamp(timestamp) {
  function renderStatus (line 29) | function renderStatus(status, t) {

FILE: web/default/src/components/RegisterForm.js
  function handleChange (line 67) | function handleChange(e) {
  function handleSubmit (line 73) | async function handleSubmit(e) {

FILE: web/default/src/components/TokensTable.js
  function renderTimestamp (line 25) | function renderTimestamp(timestamp) {
  function renderStatus (line 29) | function renderStatus(status, t) {

FILE: web/default/src/components/UsersTable.js
  function renderRole (line 23) | function renderRole(role, t) {

FILE: web/default/src/components/utils.js
  function getOAuthState (line 3) | async function getOAuthState() {
  function onGitHubOAuthClicked (line 14) | async function onGitHubOAuthClicked(github_client_id) {
  function onLarkOAuthClicked (line 22) | async function onLarkOAuthClicked(lark_client_id) {

FILE: web/default/src/constants/channel.constants.js
  constant CHANNEL_OPTIONS (line 1) | const CHANNEL_OPTIONS = [

FILE: web/default/src/constants/common.constant.js
  constant ITEMS_PER_PAGE (line 1) | const ITEMS_PER_PAGE = 10;

FILE: web/default/src/helpers/api.js
  constant API (line 4) | const API = axios.create({

FILE: web/default/src/helpers/auth-header.js
  function authHeader (line 1) | function authHeader() {

FILE: web/default/src/helpers/helper.js
  function getChannelOption (line 5) | function getChannelOption(channelId) {

FILE: web/default/src/helpers/render.js
  function renderText (line 5) | function renderText(text, limit) {
  function renderGroup (line 12) | function renderGroup(group) {
  function renderNumber (line 40) | function renderNumber(num) {
  function renderQuota (line 52) | function renderQuota(quota, t, precision = 2) {
  function renderQuotaWithPrompt (line 67) | function renderQuotaWithPrompt(quota, t) {
  function renderColorLabel (line 98) | function renderColorLabel(text) {
  function renderChannelTip (line 111) | function renderChannelTip(channelId) {

FILE: web/default/src/helpers/utils.js
  function isAdmin (line 11) | function isAdmin() {
  function isRoot (line 18) | function isRoot() {
  function getSystemName (line 25) | function getSystemName() {
  function getLogo (line 31) | function getLogo() {
  function getFooterHTML (line 37) | function getFooterHTML() {
  function copy (line 41) | async function copy(text) {
  function isMobile (line 52) | function isMobile() {
  function showError (line 76) | function showError(error) {
  function showWarning (line 106) | function showWarning(message) {
  function showSuccess (line 110) | function showSuccess(message) {
  function showInfo (line 114) | function showInfo(message) {
  function showNotice (line 118) | function showNotice(message, isHTML = false) {
  function openPage (line 126) | function openPage(url) {
  function removeTrailingSlash (line 130) | function removeTrailingSlash(url) {
  function timestamp2string (line 138) | function timestamp2string(timestamp) {
  function downloadTextAsFile (line 166) | function downloadTextAsFile(text, filename) {
  function shouldShowPrompt (line 184) | function shouldShowPrompt(id) {
  function setPromptShown (line 189) | function setPromptShown(id) {
  function loadChannelModels (line 194) | async function loadChannelModels() {
  function getChannelModels (line 204) | function getChannelModels(type) {

FILE: web/default/src/pages/Channel/EditChannel.js
  constant MODEL_MAPPING_EXAMPLE (line 9) | const MODEL_MAPPING_EXAMPLE = {
  function type2secretPrompt (line 15) | function type2secretPrompt(type, t) {
Condensed preview — 533 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,996K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 43,
    "preview": "custom: ['https://iamazing.cn/page/reward']"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 340,
    "preview": "---\nname: 报告问题\nabout: 使用简练详细的语言描述你遇到的问题\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**例行检查**\n\n[//]: # (方框内删除已有的空格,填 x 号)\n+"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 222,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: 项目群聊\n    url: https://openai.justsong.cn/\n    about: QQ 群:828520184"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 320,
    "preview": "---\nname: 功能请求\nabout: 使用简练详细的语言描述希望加入的新功能\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**例行检查**\n\n[//]: # (方框内删除已有的空"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1604,
    "preview": "name: CI\n\n# This setup assumes that you run the unit tests with code coverage in the same\n# workflow that will also prin"
  },
  {
    "path": ".github/workflows/docker-image.yml",
    "chars": 1962,
    "preview": "name: Publish Docker image\n\non:\n  push:\n    tags:\n      - 'v*.*.*'\n  workflow_dispatch:\n    inputs:\n      name:\n        "
  },
  {
    "path": ".github/workflows/linux-release.yml",
    "chars": 1848,
    "preview": "name: Linux Release\npermissions:\n  contents: write\n\non:\n  push:\n    tags:\n      - 'v*.*.*'\n      - '!*-alpha*'\n      - '"
  },
  {
    "path": ".github/workflows/macos-release.yml",
    "chars": 1459,
    "preview": "name: macOS Release\npermissions:\n  contents: write\n\non:\n  push:\n    tags:\n      - 'v*.*.*'\n      - '!*-alpha*'\n      - '"
  },
  {
    "path": ".github/workflows/windows-release.yml",
    "chars": 1481,
    "preview": "name: Windows Release\npermissions:\n  contents: write\n\non:\n  push:\n    tags:\n      - 'v*.*.*'\n      - '!*-alpha*'\n      -"
  },
  {
    "path": ".gitignore",
    "chars": 114,
    "preview": ".idea\n.vscode\nupload\n*.exe\n*.db\nbuild\n*.db-journal\nlogs\ndata\n/web/node_modules\ncmd.md\n.env\n/one-api\ntemp\n.DS_Store"
  },
  {
    "path": "Dockerfile",
    "chars": 1151,
    "preview": "FROM --platform=$BUILDPLATFORM node:16 AS builder\n\nWORKDIR /web\nCOPY ./VERSION .\nCOPY ./web .\n\nRUN npm install --prefix "
  },
  {
    "path": "LICENSE",
    "chars": 1065,
    "preview": "MIT License\n\nCopyright (c) 2023 JustSong\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
  },
  {
    "path": "README.en.md",
    "chars": 19514,
    "preview": "<p align=\"right\">\n    <a href=\"./README.md\">中文</a> | <strong>English</strong> | <a href=\"./README.ja.md\">日本語</a>\n</p>\n\n<"
  },
  {
    "path": "README.ja.md",
    "chars": 11675,
    "preview": "<p align=\"right\">\n    <a href=\"./README.md\">中文</a> | <a href=\"./README.en.md\">English</a> | <strong>日本語</strong>\n</p>\n\n<"
  },
  {
    "path": "README.md",
    "chars": 17927,
    "preview": "<p align=\"right\">\n   <strong>中文</strong> | <a href=\"./README.en.md\">English</a> | <a href=\"./README.ja.md\">日本語</a>\n</p>\n"
  },
  {
    "path": "VERSION",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "bin/migration_v0.2-v0.3.sql",
    "chars": 118,
    "preview": "UPDATE users\nSET quota = quota + (\n    SELECT SUM(remain_quota)\n    FROM tokens\n    WHERE tokens.user_id = users.id\n)\n"
  },
  {
    "path": "bin/migration_v0.3-v0.4.sql",
    "chars": 464,
    "preview": "INSERT INTO abilities (`group`, model, channel_id, enabled)\nSELECT c.`group`, m.model, c.id, 1\nFROM channels c\nCROSS JOI"
  },
  {
    "path": "bin/time_test.sh",
    "chars": 1233,
    "preview": "#!/bin/bash\n\nif [ $# -lt 3 ]; then\n  echo \"Usage: time_test.sh <domain> <key> <count> [<model>]\"\n  exit 1\nfi\n\ndomain=$1\n"
  },
  {
    "path": "common/blacklist/main.go",
    "chars": 398,
    "preview": "package blacklist\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\nvar blackList sync.Map\n\nfunc init() {\n\tblackList = sync.Map{}\n}\n\nfunc user"
  },
  {
    "path": "common/client/init.go",
    "chars": 1640,
    "preview": "package client\n\nimport (\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/songquanpeng/one-api/commo"
  },
  {
    "path": "common/config/config.go",
    "chars": 4485,
    "preview": "package config\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/songquanpeng/one-api/common/env\"\n\n\t\"g"
  },
  {
    "path": "common/constants.go",
    "chars": 208,
    "preview": "package common\n\nimport \"time\"\n\nvar StartTime = time.Now().Unix() // unit: second\nvar Version = \"v0.0.0\"            // th"
  },
  {
    "path": "common/conv/any.go",
    "chars": 80,
    "preview": "package conv\n\nfunc AsString(v any) string {\n\tstr, _ := v.(string)\n\treturn str\n}\n"
  },
  {
    "path": "common/crypto.go",
    "chars": 425,
    "preview": "package common\n\nimport \"golang.org/x/crypto/bcrypt\"\n\nfunc Password2Hash(password string) (string, error) {\n\tpasswordByte"
  },
  {
    "path": "common/ctxkey/key.go",
    "chars": 713,
    "preview": "package ctxkey\n\nconst (\n\tConfig            = \"config\"\n\tId                = \"id\"\n\tUsername          = \"username\"\n\tRole   "
  },
  {
    "path": "common/custom-event.go",
    "chars": 1664,
    "preview": "// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.\n// Use of this source code is governed by a MIT style\n// "
  },
  {
    "path": "common/database.go",
    "chars": 241,
    "preview": "package common\n\nimport (\n\t\"github.com/songquanpeng/one-api/common/env\"\n)\n\nvar UsingSQLite = false\nvar UsingPostgreSQL = "
  },
  {
    "path": "common/embed-file-system.go",
    "chars": 525,
    "preview": "package common\n\nimport (\n\t\"embed\"\n\t\"github.com/gin-contrib/static\"\n\t\"io/fs\"\n\t\"net/http\"\n)\n\n// Credit: https://github.com"
  },
  {
    "path": "common/env/helper.go",
    "chars": 775,
    "preview": "package env\n\nimport (\n\t\"os\"\n\t\"strconv\"\n)\n\nfunc Bool(env string, defaultValue bool) bool {\n\tif env == \"\" || os.Getenv(env"
  },
  {
    "path": "common/gin.go",
    "chars": 1335,
    "preview": "package common\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpe"
  },
  {
    "path": "common/helper/helper.go",
    "chars": 3157,
    "preview": "package helper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"log\"\n\t\"net\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\t"
  },
  {
    "path": "common/helper/key.go",
    "chars": 64,
    "preview": "package helper\n\nconst (\n\tRequestIdKey = \"X-Oneapi-Request-Id\"\n)\n"
  },
  {
    "path": "common/helper/time.go",
    "chars": 387,
    "preview": "package helper\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc GetTimestamp() int64 {\n\treturn time.Now().Unix()\n}\n\nfunc GetTimeString()"
  },
  {
    "path": "common/i18n/i18n.go",
    "chars": 1358,
    "preview": "package i18n\n\nimport (\n\t\"embed\"\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n//go:embed locales/*.json\nva"
  },
  {
    "path": "common/i18n/locales/en.json",
    "chars": 157,
    "preview": "{\n  \"invalid_input\": \"Invalid input, please check your input\",\n  \"send_email_failed\": \"failed to send email: \",\n  \"inval"
  },
  {
    "path": "common/i18n/locales/zh-CN.json",
    "chars": 105,
    "preview": "{\n  \"invalid_input\": \"无效的输入,请检查您的输入\",\n  \"send_email_failed\": \"发送邮件失败:\",\n  \"invalid_parameter\": \"无效的参数\"\n}\n"
  },
  {
    "path": "common/image/image.go",
    "chars": 2357,
    "preview": "package image\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"github.com/songquanpeng/one-api/common/client\"\n\t\"image\"\n\t_ \"image/"
  },
  {
    "path": "common/image/image_test.go",
    "chars": 4795,
    "preview": "package image_test\n\nimport (\n\t\"encoding/base64\"\n\t\"github.com/songquanpeng/one-api/common/client\"\n\t\"image\"\n\t_ \"image/gif\""
  },
  {
    "path": "common/init.go",
    "chars": 1547,
    "preview": "package common\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/songquanpeng/one-a"
  },
  {
    "path": "common/logger/constants.go",
    "chars": 34,
    "preview": "package logger\n\nvar LogDir string\n"
  },
  {
    "path": "common/logger/logger.go",
    "chars": 3559,
    "preview": "package logger\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\""
  },
  {
    "path": "common/message/email.go",
    "chars": 2913,
    "preview": "package message\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/smtp\"\n\t\"strings\"\n\t\"time\"\n\n"
  },
  {
    "path": "common/message/main.go",
    "chars": 480,
    "preview": "package message\n\nimport (\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n)\n\nconst (\n\tByAll           = \"all\"\n\tB"
  },
  {
    "path": "common/message/message-pusher.go",
    "chars": 1135,
    "preview": "package message\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"net/htt"
  },
  {
    "path": "common/message/template.go",
    "chars": 1166,
    "preview": "package message\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/songquanpeng/one-api/common/config\"\n)\n\n// EmailTemplate 生成美观的 HTML 邮件内容\nf"
  },
  {
    "path": "common/network/ip.go",
    "chars": 1078,
    "preview": "package network\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/logger\"\n\t\"net\"\n\t\"strings\"\n)\n\nfunc s"
  },
  {
    "path": "common/network/ip_test.go",
    "chars": 389,
    "preview": "package network\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t. \"github.com/smartystreets/goconvey/convey\"\n)\n\nfunc TestIsIpInSubnet("
  },
  {
    "path": "common/random/main.go",
    "chars": 1272,
    "preview": "package random\n\nimport (\n\t\"github.com/google/uuid\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc GetUUID() string {\n\tcode := u"
  },
  {
    "path": "common/rate-limit.go",
    "chars": 1489,
    "preview": "package common\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype InMemoryRateLimiter struct {\n\tstore              map[string]*[]int64\n\tm"
  },
  {
    "path": "common/redis.go",
    "chars": 2085,
    "preview": "package common\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-redis/redis/v8\"\n\t\"github.com/songquanpeng/"
  },
  {
    "path": "common/render/render.go",
    "chars": 614,
    "preview": "package render\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-a"
  },
  {
    "path": "common/utils/array.go",
    "chars": 239,
    "preview": "package utils\n\nfunc DeDuplication(slice []string) []string {\n\tm := make(map[string]bool)\n\tfor _, v := range slice {\n\t\tm["
  },
  {
    "path": "common/utils.go",
    "chars": 280,
    "preview": "package common\n\nimport (\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n)\n\nfunc LogQuota(quota int64) string {\n"
  },
  {
    "path": "common/validate.go",
    "chars": 143,
    "preview": "package common\n\nimport \"github.com/go-playground/validator/v10\"\n\nvar Validate *validator.Validate\n\nfunc init() {\n\tValida"
  },
  {
    "path": "common/verification.go",
    "chars": 1753,
    "preview": "package common\n\nimport (\n\t\"github.com/google/uuid\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype verificationValue struct {\n\tcode s"
  },
  {
    "path": "controller/auth/github.go",
    "chars": 5424,
    "preview": "package auth\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-contr"
  },
  {
    "path": "controller/auth/lark.go",
    "chars": 4572,
    "preview": "package auth\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-contr"
  },
  {
    "path": "controller/auth/oidc.go",
    "chars": 5176,
    "preview": "package auth\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-contr"
  },
  {
    "path": "controller/auth/wechat.go",
    "chars": 3417,
    "preview": "package auth\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\""
  },
  {
    "path": "controller/billing.go",
    "chars": 2197,
    "preview": "package controller\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/s"
  },
  {
    "path": "controller/channel-billing.go",
    "chars": 12394,
    "preview": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/songqu"
  },
  {
    "path": "controller/channel-test.go",
    "chars": 8891,
    "preview": "package controller\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest"
  },
  {
    "path": "controller/channel.go",
    "chars": 3251,
    "preview": "package controller\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/s"
  },
  {
    "path": "controller/group.go",
    "chars": 401,
    "preview": "package controller\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\tbillingratio \"github.com/songquanpeng/one-api/relay/billing/ra"
  },
  {
    "path": "controller/log.go",
    "chars": 4482,
    "preview": "package controller\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/s"
  },
  {
    "path": "controller/misc.go",
    "chars": 6686,
    "preview": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/songquanpeng/one-api/common\"\n\t"
  },
  {
    "path": "controller/model.go",
    "chars": 5822,
    "preview": "package controller\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common/ctxkey\"\n\t\"githu"
  },
  {
    "path": "controller/option.go",
    "chars": 2346,
    "preview": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t"
  },
  {
    "path": "controller/redemption.go",
    "chars": 4015,
    "preview": "package controller\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/s"
  },
  {
    "path": "controller/relay.go",
    "chars": 4625,
    "preview": "package controller\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/song"
  },
  {
    "path": "controller/token.go",
    "chars": 5730,
    "preview": "package controller\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"githu"
  },
  {
    "path": "controller/user.go",
    "chars": 17239,
    "preview": "package controller\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gin-contrib/sessions\"\n"
  },
  {
    "path": "docker-compose.yml",
    "chars": 1431,
    "preview": "version: '3.4'\n\nservices:\n  one-api:\n    image: \"${REGISTRY:-docker.io}/justsong/one-api:latest\"\n    container_name: one"
  },
  {
    "path": "docs/API.md",
    "chars": 1089,
    "preview": "# 使用 API 操控 & 扩展 One API\n> 欢迎提交 PR 在此放上你的拓展项目。\n\n例如,虽然 One API 本身没有直接支持支付,但是你可以通过系统扩展的 API 来实现支付功能。\n\n又或者你想自定义渠道管理策略,也可以通过"
  },
  {
    "path": "go.mod",
    "chars": 4965,
    "preview": "module github.com/songquanpeng/one-api\n\ngo 1.20\n\nrequire (\n\tcloud.google.com/go/iam v1.1.10\n\tgithub.com/aws/aws-sdk-go-v"
  },
  {
    "path": "go.sum",
    "chars": 29482,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go/auth v0.6.1 h1:T0"
  },
  {
    "path": "main.go",
    "chars": 3438,
    "preview": "package main\n\nimport (\n\t\"embed\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/gin-contrib/sessions\"\n\t\"github.com/gin-contrib/ses"
  },
  {
    "path": "middleware/auth.go",
    "chars": 4231,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-contrib/sessions\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquan"
  },
  {
    "path": "middleware/cache.go",
    "chars": 293,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc Cache() func(c *gin.Context) {\n\treturn func(c *gin.Cont"
  },
  {
    "path": "middleware/cors.go",
    "chars": 355,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gin-contrib/cors\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc CORS() gin.HandlerFunc {"
  },
  {
    "path": "middleware/distributor.go",
    "chars": 2977,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/songquanpeng/one-a"
  },
  {
    "path": "middleware/gzip.go",
    "chars": 544,
    "preview": "package middleware\n\nimport (\n\t\"compress/gzip\"\n\t\"github.com/gin-gonic/gin\"\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc GzipDecodeMiddleware"
  },
  {
    "path": "middleware/language.go",
    "chars": 412,
    "preview": "package middleware\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/songquanpeng/one-api/common/i18n\"\n)\n\n"
  },
  {
    "path": "middleware/logger.go",
    "chars": 579,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common/helper\"\n)\n\nfunc"
  },
  {
    "path": "middleware/rate-limit.go",
    "chars": 3231,
    "preview": "package middleware\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/songquanpe"
  },
  {
    "path": "middleware/recover.go",
    "chars": 1015,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"github.com/s"
  },
  {
    "path": "middleware/request-id.go",
    "chars": 391,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/songquanpeng/one-api/common/helper\"\n)\n\nfunc Reque"
  },
  {
    "path": "middleware/turnstile-check.go",
    "chars": 1785,
    "preview": "package middleware\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/gin-contrib/sessions\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.co"
  },
  {
    "path": "middleware/utils.go",
    "chars": 1633,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"github.com/s"
  },
  {
    "path": "model/ability.go",
    "chars": 3327,
    "preview": "package model\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"gith"
  },
  {
    "path": "model/cache.go",
    "chars": 7246,
    "preview": "package model\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"github."
  },
  {
    "path": "model/channel.go",
    "chars": 6861,
    "preview": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/songquanp"
  },
  {
    "path": "model/log.go",
    "chars": 7725,
    "preview": "package model\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"github.com/songq"
  },
  {
    "path": "model/main.go",
    "chars": 5455,
    "preview": "package model\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"github.com/songquanpeng/one-a"
  },
  {
    "path": "model/option.go",
    "chars": 9213,
    "preview": "package model\n\nimport (\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/songquanpeng/one-api/common/logger"
  },
  {
    "path": "model/redemption.go",
    "chars": 3545,
    "preview": "package model\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"github"
  },
  {
    "path": "model/token.go",
    "chars": 8442,
    "preview": "package model\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"github.com/songqu"
  },
  {
    "path": "model/user.go",
    "chars": 13361,
    "preview": "package model\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/songquanpeng/one-api/commo"
  },
  {
    "path": "model/utils.go",
    "chars": 2054,
    "preview": "package model\n\nimport (\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/songquanpeng/one-api/common/logger"
  },
  {
    "path": "monitor/channel.go",
    "chars": 2530,
    "preview": "package monitor\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/songquanpeng/one-api/com"
  },
  {
    "path": "monitor/manage.go",
    "chars": 1564,
    "preview": "package monitor\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/songquanpeng/one-api/common/config\"\n\t\"github.com/songquan"
  },
  {
    "path": "monitor/metric.go",
    "chars": 1684,
    "preview": "package monitor\n\nimport (\n\t\"github.com/songquanpeng/one-api/common/config\"\n)\n\nvar store = make(map[int][]bool)\nvar metri"
  },
  {
    "path": "one-api.service",
    "chars": 468,
    "preview": "# File path: /etc/systemd/system/one-api.service\n# sudo systemctl daemon-reload\n# sudo systemctl start one-api\n# sudo sy"
  },
  {
    "path": "pull_request_template.md",
    "chars": 256,
    "preview": "[//]: # (请按照以下格式关联 issue)\n[//]: # (请在提交 PR 前确认所提交的功能可用,需要附上截图,谢谢)\n[//]: # (项目维护者一般仅在周末处理 PR,因此如若未能及时回复希望能理解)\n[//]: # (开发"
  },
  {
    "path": "relay/adaptor/ai360/constants.go",
    "chars": 136,
    "preview": "package ai360\n\nvar ModelList = []string{\n\t\"360GPT_S2_V9\",\n\t\"embedding-bert-512-v1\",\n\t\"embedding_s1_v1\",\n\t\"semantic_simil"
  },
  {
    "path": "relay/adaptor/aiproxy/adaptor.go",
    "chars": 1738,
    "preview": "package aiproxy\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/relay/adaptor\"\n"
  },
  {
    "path": "relay/adaptor/aiproxy/constants.go",
    "chars": 156,
    "preview": "package aiproxy\n\nimport \"github.com/songquanpeng/one-api/relay/adaptor/openai\"\n\nvar ModelList = []string{\"\"}\n\nfunc init("
  },
  {
    "path": "relay/adaptor/aiproxy/main.go",
    "chars": 5779,
    "preview": "package aiproxy\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/render\"\n\t\"io\"\n\t\"net/"
  },
  {
    "path": "relay/adaptor/aiproxy/model.go",
    "chars": 778,
    "preview": "package aiproxy\n\ntype LibraryRequest struct {\n\tModel     string `json:\"model\"`\n\tQuery     string `json:\"query\"`\n\tLibrary"
  },
  {
    "path": "relay/adaptor/ali/adaptor.go",
    "chars": 2865,
    "preview": "package ali\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/relay/adaptor\"\n\t\"gi"
  },
  {
    "path": "relay/adaptor/ali/constants.go",
    "chars": 2107,
    "preview": "package ali\n\nvar ModelList = []string{\n\t\"qwen-turbo\", \"qwen-turbo-latest\",\n\t\"qwen-plus\", \"qwen-plus-latest\",\n\t\"qwen-max\""
  },
  {
    "path": "relay/adaptor/ali/image.go",
    "chars": 5011,
    "preview": "package ali\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/song"
  },
  {
    "path": "relay/adaptor/ali/main.go",
    "chars": 8245,
    "preview": "package ali\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"github.com/songquanpeng/one-api/common/ctxkey\"\n\t\"github.com/songquanpe"
  },
  {
    "path": "relay/adaptor/ali/model.go",
    "chars": 4551,
    "preview": "package ali\n\nimport (\n\t\"github.com/songquanpeng/one-api/relay/adaptor/openai\"\n\t\"github.com/songquanpeng/one-api/relay/mo"
  },
  {
    "path": "relay/adaptor/alibailian/constants.go",
    "chars": 355,
    "preview": "package alibailian\n\n// https://help.aliyun.com/zh/model-studio/getting-started/models\n\nvar ModelList = []string{\n\t\"qwen-"
  },
  {
    "path": "relay/adaptor/alibailian/main.go",
    "chars": 524,
    "preview": "package alibailian\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n\t\"github.com/songquanpeng/one-api/rel"
  },
  {
    "path": "relay/adaptor/anthropic/adaptor.go",
    "chars": 2126,
    "preview": "package anthropic\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/song"
  },
  {
    "path": "relay/adaptor/anthropic/constants.go",
    "chars": 331,
    "preview": "package anthropic\n\nvar ModelList = []string{\n\t\"claude-instant-1.2\", \"claude-2.0\", \"claude-2.1\",\n\t\"claude-3-haiku-2024030"
  },
  {
    "path": "relay/adaptor/anthropic/main.go",
    "chars": 11598,
    "preview": "package anthropic\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/render\"\n\t\"io\"\n\t\"ne"
  },
  {
    "path": "relay/adaptor/anthropic/model.go",
    "chars": 2790,
    "preview": "package anthropic\n\n// https://docs.anthropic.com/claude/reference/messages_post\n\ntype Metadata struct {\n\tUserId string `"
  },
  {
    "path": "relay/adaptor/aws/adaptor.go",
    "chars": 2193,
    "preview": "package aws\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/cr"
  },
  {
    "path": "relay/adaptor/aws/claude/adapter.go",
    "chars": 1089,
    "preview": "package aws\n\nimport (\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrockruntime\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pk"
  },
  {
    "path": "relay/adaptor/aws/claude/main.go",
    "chars": 7010,
    "preview": "// Package aws provides the AWS adaptor for the relay service.\npackage aws\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\""
  },
  {
    "path": "relay/adaptor/aws/claude/model.go",
    "chars": 954,
    "preview": "package aws\n\nimport \"github.com/songquanpeng/one-api/relay/adaptor/anthropic\"\n\n// Request is the request to AWS Claude\n/"
  },
  {
    "path": "relay/adaptor/aws/llama3/adapter.go",
    "chars": 1018,
    "preview": "package aws\n\nimport (\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrockruntime\"\n\t\"github.com/songquanpeng/one-api/common/ct"
  },
  {
    "path": "relay/adaptor/aws/llama3/main.go",
    "chars": 7275,
    "preview": "// Package aws provides the AWS adaptor for the relay service.\npackage aws\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\""
  },
  {
    "path": "relay/adaptor/aws/llama3/main_test.go",
    "chars": 1291,
    "preview": "package aws_test\n\nimport (\n\t\"testing\"\n\n\taws \"github.com/songquanpeng/one-api/relay/adaptor/aws/llama3\"\n\trelaymodel \"gith"
  },
  {
    "path": "relay/adaptor/aws/llama3/model.go",
    "chars": 1076,
    "preview": "package aws\n\n// Request is the request to AWS Llama3\n//\n// https://docs.aws.amazon.com/bedrock/latest/userguide/model-pa"
  },
  {
    "path": "relay/adaptor/aws/registry.go",
    "chars": 733,
    "preview": "package aws\n\nimport (\n\tclaude \"github.com/songquanpeng/one-api/relay/adaptor/aws/claude\"\n\tllama3 \"github.com/songquanpen"
  },
  {
    "path": "relay/adaptor/aws/utils/adaptor.go",
    "chars": 1425,
    "preview": "package utils\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/"
  },
  {
    "path": "relay/adaptor/aws/utils/utils.go",
    "chars": 305,
    "preview": "package utils\n\nimport (\n\t\"net/http\"\n\n\trelaymodel \"github.com/songquanpeng/one-api/relay/model\"\n)\n\nfunc WrapErr(err error"
  },
  {
    "path": "relay/adaptor/baichuan/constants.go",
    "chars": 119,
    "preview": "package baichuan\n\nvar ModelList = []string{\n\t\"Baichuan2-Turbo\",\n\t\"Baichuan2-Turbo-192k\",\n\t\"Baichuan-Text-Embedding\",\n}\n"
  },
  {
    "path": "relay/adaptor/baidu/adaptor.go",
    "chars": 3689,
    "preview": "package baidu\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n\t\"github.com/songquanpeng/one-api"
  },
  {
    "path": "relay/adaptor/baidu/constants.go",
    "chars": 340,
    "preview": "package baidu\n\nvar ModelList = []string{\n\t\"ERNIE-4.0-8K\",\n\t\"ERNIE-3.5-8K\",\n\t\"ERNIE-3.5-8K-0205\",\n\t\"ERNIE-3.5-8K-1222\",\n\t"
  },
  {
    "path": "relay/adaptor/baidu/main.go",
    "chars": 9550,
    "preview": "package baidu\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/render\"\n\t\"io"
  },
  {
    "path": "relay/adaptor/baidu/model.go",
    "chars": 1319,
    "preview": "package baidu\n\nimport (\n\t\"github.com/songquanpeng/one-api/relay/model\"\n\t\"time\"\n)\n\ntype ChatResponse struct {\n\tId        "
  },
  {
    "path": "relay/adaptor/baiduv2/constants.go",
    "chars": 898,
    "preview": "package baiduv2\n\n// https://console.bce.baidu.com/support/?_=1692863460488&timestamp=1739074632076#/api?product=QIANFAN&"
  },
  {
    "path": "relay/adaptor/baiduv2/main.go",
    "chars": 398,
    "preview": "package baiduv2\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n\t\"github.com/songquanpeng/one-api/relay/"
  },
  {
    "path": "relay/adaptor/cloudflare/adaptor.go",
    "chars": 2971,
    "preview": "package cloudflare\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/son"
  },
  {
    "path": "relay/adaptor/cloudflare/constant.go",
    "chars": 1293,
    "preview": "package cloudflare\n\nvar ModelList = []string{\n\t\"@cf/meta/llama-3.1-8b-instruct\",\n\t\"@cf/meta/llama-2-7b-chat-fp16\",\n\t\"@cf"
  },
  {
    "path": "relay/adaptor/cloudflare/main.go",
    "chars": 3389,
    "preview": "package cloudflare\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/songquanpeng/one-api/c"
  },
  {
    "path": "relay/adaptor/cloudflare/model.go",
    "chars": 489,
    "preview": "package cloudflare\n\nimport \"github.com/songquanpeng/one-api/relay/model\"\n\ntype Request struct {\n\tMessages    []model.Mes"
  },
  {
    "path": "relay/adaptor/cohere/adaptor.go",
    "chars": 1686,
    "preview": "package cohere\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-a"
  },
  {
    "path": "relay/adaptor/cohere/constant.go",
    "chars": 279,
    "preview": "package cohere\n\nvar ModelList = []string{\n\t\"command\", \"command-nightly\",\n\t\"command-light\", \"command-light-nightly\",\n\t\"co"
  },
  {
    "path": "relay/adaptor/cohere/main.go",
    "chars": 6523,
    "preview": "package cohere\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/render\"\n\t\"io\"\n\t\"net/h"
  },
  {
    "path": "relay/adaptor/cohere/model.go",
    "chars": 4902,
    "preview": "package cohere\n\ntype Request struct {\n\tMessage          string        `json:\"message\" required:\"true\"`\n\tModel           "
  },
  {
    "path": "relay/adaptor/common.go",
    "chars": 1445,
    "preview": "package adaptor\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common/client\"\n"
  },
  {
    "path": "relay/adaptor/coze/adaptor.go",
    "chars": 2061,
    "preview": "package coze\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/relay/adaptor\"\n\t\"g"
  },
  {
    "path": "relay/adaptor/coze/constant/contenttype/define.go",
    "chars": 46,
    "preview": "package contenttype\n\nconst (\n\tText = \"text\"\n)\n"
  },
  {
    "path": "relay/adaptor/coze/constant/event/define.go",
    "chars": 83,
    "preview": "package event\n\nconst (\n\tMessage = \"message\"\n\tDone    = \"done\"\n\tError   = \"error\"\n)\n"
  },
  {
    "path": "relay/adaptor/coze/constant/messagetype/define.go",
    "chars": 76,
    "preview": "package messagetype\n\nconst (\n\tAnswer   = \"answer\"\n\tFollowUp = \"follow_up\"\n)\n"
  },
  {
    "path": "relay/adaptor/coze/constants.go",
    "chars": 41,
    "preview": "package coze\n\nvar ModelList = []string{}\n"
  },
  {
    "path": "relay/adaptor/coze/helper.go",
    "chars": 202,
    "preview": "package coze\n\nimport \"github.com/songquanpeng/one-api/relay/adaptor/coze/constant/event\"\n\nfunc event2StopReason(e *strin"
  },
  {
    "path": "relay/adaptor/coze/main.go",
    "chars": 5629,
    "preview": "package coze\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/render\"\n\t\"io\"\n\t\"net/htt"
  },
  {
    "path": "relay/adaptor/coze/model.go",
    "chars": 1247,
    "preview": "package coze\n\ntype Message struct {\n\tRole        string `json:\"role\"`\n\tType        string `json:\"type\"`\n\tContent     str"
  },
  {
    "path": "relay/adaptor/deepl/adaptor.go",
    "chars": 1873,
    "preview": "package deepl\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/relay/adaptor\"\n\t\""
  },
  {
    "path": "relay/adaptor/deepl/constants.go",
    "chars": 145,
    "preview": "package deepl\n\n// https://developers.deepl.com/docs/api-reference/glossaries\n\nvar ModelList = []string{\n\t\"deepl-zh\",\n\t\"d"
  },
  {
    "path": "relay/adaptor/deepl/helper.go",
    "chars": 186,
    "preview": "package deepl\n\nimport \"strings\"\n\nfunc parseLangFromModelName(modelName string) string {\n\tparts := strings.Split(modelNam"
  },
  {
    "path": "relay/adaptor/deepl/main.go",
    "chars": 4526,
    "preview": "package deepl\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/common\"\n\t\"github."
  },
  {
    "path": "relay/adaptor/deepl/model.go",
    "chars": 416,
    "preview": "package deepl\n\ntype Request struct {\n\tText       []string `json:\"text\"`\n\tTargetLang string   `json:\"target_lang\"`\n}\n\ntyp"
  },
  {
    "path": "relay/adaptor/deepseek/constants.go",
    "chars": 86,
    "preview": "package deepseek\n\nvar ModelList = []string{\n\t\"deepseek-chat\",\n\t\"deepseek-reasoner\",\n}\n"
  },
  {
    "path": "relay/adaptor/doubao/constants.go",
    "chars": 249,
    "preview": "package doubao\n\n// https://console.volcengine.com/ark/region:ark+cn-beijing/model\n\nvar ModelList = []string{\n\t\"Doubao-pr"
  },
  {
    "path": "relay/adaptor/doubao/main.go",
    "chars": 490,
    "preview": "package doubao\n\nimport (\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n\t\"github.com/songquanpeng/one-api/relay/re"
  },
  {
    "path": "relay/adaptor/gemini/adaptor.go",
    "chars": 2812,
    "preview": "package gemini\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songqua"
  },
  {
    "path": "relay/adaptor/gemini/constants.go",
    "chars": 1031,
    "preview": "package gemini\n\nimport (\n\t\"github.com/songquanpeng/one-api/relay/adaptor/geminiv2\"\n)\n\n// https://ai.google.dev/models/ge"
  },
  {
    "path": "relay/adaptor/gemini/main.go",
    "chars": 12707,
    "preview": "package gemini\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/songquanpeng/one-ap"
  },
  {
    "path": "relay/adaptor/gemini/model.go",
    "chars": 2351,
    "preview": "package gemini\n\ntype ChatRequest struct {\n\tContents          []ChatContent        `json:\"contents\"`\n\tSafetySettings    ["
  },
  {
    "path": "relay/adaptor/geminiv2/constants.go",
    "chars": 453,
    "preview": "package geminiv2\n\n// https://ai.google.dev/models/gemini\n\nvar ModelList = []string{\n\t\"gemini-pro\", \"gemini-1.0-pro\",\n\t//"
  },
  {
    "path": "relay/adaptor/geminiv2/main.go",
    "chars": 319,
    "preview": "package geminiv2\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n)\n\nfunc GetRequestURL(meta *"
  },
  {
    "path": "relay/adaptor/groq/constants.go",
    "chars": 683,
    "preview": "package groq\n\n// https://console.groq.com/docs/models\n\nvar ModelList = []string{\n\t\"gemma2-9b-it\",\n\t\"llama-3.1-70b-versat"
  },
  {
    "path": "relay/adaptor/interface.go",
    "chars": 764,
    "preview": "package adaptor\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n\t\"github.com/songqua"
  },
  {
    "path": "relay/adaptor/lingyiwanwu/constants.go",
    "chars": 148,
    "preview": "package lingyiwanwu\n\n// https://platform.lingyiwanwu.com/docs\n\nvar ModelList = []string{\n\t\"yi-34b-chat-0205\",\n\t\"yi-34b-c"
  },
  {
    "path": "relay/adaptor/minimax/constants.go",
    "chars": 255,
    "preview": "package minimax\n\n// https://www.minimaxi.com/document/guides/chat-model/V2?id=65e0736ab2845de20908e2dd\n\nvar ModelList = "
  },
  {
    "path": "relay/adaptor/minimax/main.go",
    "chars": 384,
    "preview": "package minimax\n\nimport (\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n\t\"github.com/songquanpeng/one-api/relay/r"
  },
  {
    "path": "relay/adaptor/mistral/constants.go",
    "chars": 181,
    "preview": "package mistral\n\nvar ModelList = []string{\n\t\"open-mistral-7b\",\n\t\"open-mixtral-8x7b\",\n\t\"mistral-small-latest\",\n\t\"mistral-"
  },
  {
    "path": "relay/adaptor/moonshot/constants.go",
    "chars": 106,
    "preview": "package moonshot\n\nvar ModelList = []string{\n\t\"moonshot-v1-8k\",\n\t\"moonshot-v1-32k\",\n\t\"moonshot-v1-128k\",\n}\n"
  },
  {
    "path": "relay/adaptor/novita/constants.go",
    "chars": 521,
    "preview": "package novita\n\n// https://novita.ai/llm-api\n\nvar ModelList = []string{\n\t\"meta-llama/llama-3-8b-instruct\",\n\t\"meta-llama/"
  },
  {
    "path": "relay/adaptor/novita/main.go",
    "chars": 374,
    "preview": "package novita\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n\t\"github.com/songquanpeng/one-api/relay/r"
  },
  {
    "path": "relay/adaptor/ollama/adaptor.go",
    "chars": 2109,
    "preview": "package ollama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/relay/meta\"\n\t\"github.com/songquanpeng/one-ap"
  },
  {
    "path": "relay/adaptor/ollama/constants.go",
    "chars": 167,
    "preview": "package ollama\n\nvar ModelList = []string{\n\t\"codellama:7b-instruct\",\n\t\"llama2:7b\",\n\t\"llama2:latest\",\n\t\"llama3:latest\",\n\t\""
  },
  {
    "path": "relay/adaptor/ollama/main.go",
    "chars": 7988,
    "preview": "package ollama\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/render\"\n\t\""
  },
  {
    "path": "relay/adaptor/ollama/model.go",
    "chars": 1919,
    "preview": "package ollama\n\ntype Options struct {\n\tSeed             int      `json:\"seed,omitempty\"`\n\tTemperature      *float64 `jso"
  },
  {
    "path": "relay/adaptor/openai/adaptor.go",
    "chars": 4934,
    "preview": "package openai\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/songqu"
  },
  {
    "path": "relay/adaptor/openai/compatible.go",
    "chars": 3056,
    "preview": "package openai\n\nimport (\n\t\"github.com/songquanpeng/one-api/relay/adaptor/ai360\"\n\t\"github.com/songquanpeng/one-api/relay/"
  },
  {
    "path": "relay/adaptor/openai/constants.go",
    "chars": 1061,
    "preview": "package openai\n\nvar ModelList = []string{\n\t\"gpt-3.5-turbo\", \"gpt-3.5-turbo-0301\", \"gpt-3.5-turbo-0613\", \"gpt-3.5-turbo-1"
  },
  {
    "path": "relay/adaptor/openai/helper.go",
    "chars": 1121,
    "preview": "package openai\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/songquanpeng/one-api/relay/channeltype\"\n\t\"github.com/songquanpe"
  },
  {
    "path": "relay/adaptor/openai/image.go",
    "chars": 1225,
    "preview": "package openai\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/relay/m"
  },
  {
    "path": "relay/adaptor/openai/main.go",
    "chars": 4823,
    "preview": "package openai\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/songquanpeng/one-"
  },
  {
    "path": "relay/adaptor/openai/model.go",
    "chars": 4493,
    "preview": "package openai\n\nimport \"github.com/songquanpeng/one-api/relay/model\"\n\ntype TextContent struct {\n\tType string `json:\"type"
  },
  {
    "path": "relay/adaptor/openai/token.go",
    "chars": 7749,
    "preview": "package openai\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n\n\t\"github.com/pkoukk/tiktoken-go\"\n\n\t\"github.com/songquanpen"
  },
  {
    "path": "relay/adaptor/openai/util.go",
    "chars": 479,
    "preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/songquanpeng/one-api/common/logger\"\n\t\"github.com/songquanpeng/o"
  },
  {
    "path": "relay/adaptor/openrouter/constants.go",
    "chars": 7429,
    "preview": "package openrouter\n\nvar ModelList = []string{\n\t\"01-ai/yi-large\",\n\t\"aetherwiing/mn-starcannon-12b\",\n\t\"ai21/jamba-1-5-larg"
  },
  {
    "path": "relay/adaptor/palm/adaptor.go",
    "chars": 1843,
    "preview": "package palm\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/songquanpeng/one-api/relay/adaptor\"\n\t\"g"
  },
  {
    "path": "relay/adaptor/palm/constants.go",
    "chars": 53,
    "preview": "package palm\n\nvar ModelList = []string{\n\t\"PaLM-2\",\n}\n"
  },
  {
    "path": "relay/adaptor/palm/model.go",
    "chars": 934,
    "preview": "package palm\n\nimport (\n\t\"github.com/songquanpeng/one-api/relay/model\"\n)\n\ntype ChatMessage struct {\n\tAuthor  string `json"
  },
  {
    "path": "relay/adaptor/palm/palm.go",
    "chars": 5759,
    "preview": "package palm\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/songquanpeng/one-api/common/render\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"git"
  },
  {
    "path": "relay/adaptor/proxy/adaptor.go",
    "chars": 2345,
    "preview": "package proxy\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pkg/errors\"\n\t\"gith"
  },
  {
    "path": "relay/adaptor/replicate/adaptor.go",
    "chars": 3830,
    "preview": "package replicate\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github."
  },
  {
    "path": "relay/adaptor/replicate/chat.go",
    "chars": 4905,
    "preview": "package replicate\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n"
  }
]

// ... and 333 more files (download for full content)

About this extraction

This page contains the full source code of the songquanpeng/one-api GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 533 files (1.8 MB), approximately 496.8k tokens, and a symbol index with 1460 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!