mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 13:18:18 +08:00
@@ -142,6 +142,7 @@ func (s *CNProviderQuotaService) queryUsageForAccount(ctx context.Context, accou
|
||||
var (
|
||||
targetURL string
|
||||
authHeader string
|
||||
zhipuOrg string
|
||||
)
|
||||
switch provider {
|
||||
case PlatformKimi:
|
||||
@@ -150,6 +151,13 @@ func (s *CNProviderQuotaService) queryUsageForAccount(ctx context.Context, accou
|
||||
case PlatformZhipu:
|
||||
targetURL = zhipuQuotaURL(baseURL)
|
||||
authHeader = apiKey // 智谱额度端点鉴权不加 Bearer 前缀
|
||||
// 团队版 GLM Coding Plan:额度端点需 ?type=2 + 组织/项目请求头,
|
||||
// 否则官方 API 回「当前用户不存在coding plan」。组织 ID 存在即视为
|
||||
// 团队版(个人版凭据不含该字段,走原个人版查询路径)。
|
||||
zhipuOrg = strings.TrimSpace(account.GetCredential("zhipu_organization"))
|
||||
if zhipuOrg != "" {
|
||||
targetURL += "?type=2"
|
||||
}
|
||||
}
|
||||
|
||||
// 探测发起前过出站 URL 安全策略(与网关转发/Grok 探测同一套校验):
|
||||
@@ -172,6 +180,12 @@ func (s *CNProviderQuotaService) queryUsageForAccount(ctx context.Context, accou
|
||||
if provider == PlatformZhipu {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept-Language", "en-US,en")
|
||||
if zhipuOrg != "" {
|
||||
req.Header.Set("bigmodel-organization", zhipuOrg)
|
||||
if project := strings.TrimSpace(account.GetCredential("zhipu_project")); project != "" {
|
||||
req.Header.Set("bigmodel-project", project)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 探测与真实转发保持同一套账号级请求头覆写,避免探测通过但转发失败。
|
||||
account.ApplyHeaderOverrides(req.Header)
|
||||
|
||||
@@ -547,6 +547,36 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zhipu 团队版 Coding Plan:组织/项目 ID(可选,填写后额度探测走团队版端点) -->
|
||||
<div v-if="form.platform === 'zhipu' && accountMode === 'coding'" class="mt-4">
|
||||
<div class="flex items-center">
|
||||
<label class="input-label">{{ t('admin.accounts.cnProviders.zhipuTeam.title') }}</label>
|
||||
<HelpTooltip trigger="click" width-class="w-80">
|
||||
<p class="mb-1 font-medium">{{ t('admin.accounts.cnProviders.zhipuTeam.help.title') }}</p>
|
||||
<ol class="list-decimal space-y-1 pl-4">
|
||||
<li>{{ t('admin.accounts.cnProviders.zhipuTeam.help.step1') }}</li>
|
||||
<li>{{ t('admin.accounts.cnProviders.zhipuTeam.help.step2') }}</li>
|
||||
<li>{{ t('admin.accounts.cnProviders.zhipuTeam.help.step3') }}</li>
|
||||
<li>{{ t('admin.accounts.cnProviders.zhipuTeam.help.step4') }}</li>
|
||||
</ol>
|
||||
<p class="mt-2 break-all rounded bg-black/20 p-1.5 font-mono text-[11px] leading-relaxed">
|
||||
{{ t('admin.accounts.cnProviders.zhipuTeam.help.example') }}
|
||||
</p>
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
<div class="mt-2 grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.cnProviders.zhipuTeam.organization') }}</label>
|
||||
<input v-model="zhipuOrganization" type="text" class="input" :placeholder="t('admin.accounts.cnProviders.zhipuTeam.organizationPlaceholder')" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.cnProviders.zhipuTeam.project') }}</label>
|
||||
<input v-model="zhipuProject" type="text" class="input" :placeholder="t('admin.accounts.cnProviders.zhipuTeam.projectPlaceholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="input-hint mt-2">{{ t('admin.accounts.cnProviders.zhipuTeam.hint') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Account Type Selection (Gemini) -->
|
||||
<div v-if="form.platform === 'gemini'">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -3764,6 +3794,7 @@ import type {
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import HelpTooltip from '@/components/common/HelpTooltip.vue'
|
||||
import PlatformIcon from '@/components/common/PlatformIcon.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import ProxySelector from '@/components/common/ProxySelector.vue'
|
||||
@@ -3969,6 +4000,9 @@ const accountMode = ref<CnAccountMode>('payg')
|
||||
// API 协议决定转发端点与格式:cc=现有转换链,anthropic=原生直通(Claude Code),
|
||||
// responses=deepseek 原生 Responses 端点(Codex)。与账号类型正交。
|
||||
const apiProtocol = ref<CnApiProtocol>('adaptive')
|
||||
// 智谱团队版 Coding Plan:组织/项目 ID,写入 credentials 供额度探测切换团队端点
|
||||
const zhipuOrganization = ref('')
|
||||
const zhipuProject = ref('')
|
||||
const adaptiveBaseUrls = ref<Record<CnNativeApiProtocol, string>>({
|
||||
chat_completions: '',
|
||||
anthropic: '',
|
||||
@@ -5544,6 +5578,11 @@ const handleSubmit = async () => {
|
||||
if (apiProtocol.value !== 'adaptive' && resolvedCNBase) {
|
||||
credentials.base_url = resolvedCNBase
|
||||
}
|
||||
// 智谱团队版 Coding Plan:组织/项目 ID 写入凭据(非空才写)
|
||||
if (form.platform === 'zhipu' && accountMode.value === 'coding') {
|
||||
if (zhipuOrganization.value.trim()) credentials.zhipu_organization = zhipuOrganization.value.trim()
|
||||
if (zhipuProject.value.trim()) credentials.zhipu_project = zhipuProject.value.trim()
|
||||
}
|
||||
}
|
||||
|
||||
// Add model mapping if configured(OpenAI 开启自动透传时不应用)
|
||||
|
||||
@@ -118,6 +118,35 @@
|
||||
</div>
|
||||
<p class="input-hint">{{ t(`admin.accounts.cnProviders.apiProtocol.${cnProtocolDescKey}Desc`) }}</p>
|
||||
</div>
|
||||
<!-- Zhipu 团队版 Coding Plan:组织/项目 ID(可选,填写后用量查询走团队版端点) -->
|
||||
<div v-if="account.platform === 'zhipu' && editAccountMode === 'coding'">
|
||||
<div class="flex items-center">
|
||||
<label class="input-label">{{ t('admin.accounts.cnProviders.zhipuTeam.title') }}</label>
|
||||
<HelpTooltip trigger="click" width-class="w-80">
|
||||
<p class="mb-1 font-medium">{{ t('admin.accounts.cnProviders.zhipuTeam.help.title') }}</p>
|
||||
<ol class="list-decimal space-y-1 pl-4">
|
||||
<li>{{ t('admin.accounts.cnProviders.zhipuTeam.help.step1') }}</li>
|
||||
<li>{{ t('admin.accounts.cnProviders.zhipuTeam.help.step2') }}</li>
|
||||
<li>{{ t('admin.accounts.cnProviders.zhipuTeam.help.step3') }}</li>
|
||||
<li>{{ t('admin.accounts.cnProviders.zhipuTeam.help.step4') }}</li>
|
||||
</ol>
|
||||
<p class="mt-2 break-all rounded bg-black/20 p-1.5 font-mono text-[11px] leading-relaxed">
|
||||
{{ t('admin.accounts.cnProviders.zhipuTeam.help.example') }}
|
||||
</p>
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
<div class="mt-2 grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.cnProviders.zhipuTeam.organization') }}</label>
|
||||
<input v-model="editZhipuOrganization" type="text" class="input" :placeholder="t('admin.accounts.cnProviders.zhipuTeam.organizationPlaceholder')" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.cnProviders.zhipuTeam.project') }}</label>
|
||||
<input v-model="editZhipuProject" type="text" class="input" :placeholder="t('admin.accounts.cnProviders.zhipuTeam.projectPlaceholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="input-hint mt-2">{{ t('admin.accounts.cnProviders.zhipuTeam.hint') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.apiKey') }}</label>
|
||||
<input
|
||||
@@ -2854,6 +2883,7 @@ import type {
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import HelpTooltip from '@/components/common/HelpTooltip.vue'
|
||||
import Toggle from '@/components/common/Toggle.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import ProxySelector from '@/components/common/ProxySelector.vue'
|
||||
@@ -2987,6 +3017,9 @@ const cnPresetPlatform = computed<'kimi' | 'zhipu' | 'deepseek'>(() => {
|
||||
})
|
||||
const editApiProtocol = ref<CnApiProtocol>('adaptive')
|
||||
const editAccountMode = ref<CnAccountMode>('payg')
|
||||
// 智谱团队版 Coding Plan:组织/项目 ID,写入 credentials 供额度探测切换团队端点
|
||||
const editZhipuOrganization = ref('')
|
||||
const editZhipuProject = ref('')
|
||||
const editAdaptiveBaseUrls = ref<Record<CnNativeApiProtocol, string>>({
|
||||
chat_completions: '',
|
||||
anthropic: '',
|
||||
@@ -3939,6 +3972,11 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
nextAdaptiveBaseUrls[legacyProtocol] = legacyBaseUrl
|
||||
}
|
||||
editAdaptiveBaseUrls.value = nextAdaptiveBaseUrls
|
||||
// 智谱团队版 Coding Plan:回填组织/项目 ID
|
||||
if (newAccount.platform === 'zhipu') {
|
||||
editZhipuOrganization.value = typeof credentials.zhipu_organization === 'string' ? credentials.zhipu_organization : ''
|
||||
editZhipuProject.value = typeof credentials.zhipu_project === 'string' ? credentials.zhipu_project : ''
|
||||
}
|
||||
}
|
||||
const platformDefaultUrl =
|
||||
newAccount.platform === 'openai'
|
||||
@@ -4648,6 +4686,19 @@ const handleSubmit = async () => {
|
||||
} else {
|
||||
delete newCredentials.api_base_urls
|
||||
}
|
||||
// 智谱团队版 Coding Plan:组织/项目 ID 写入凭据(非空才写,清空即移除回落个人版路径)
|
||||
if (props.account.platform === 'zhipu') {
|
||||
const org = editZhipuOrganization.value.trim()
|
||||
const project = editZhipuProject.value.trim()
|
||||
if (org) {
|
||||
newCredentials.zhipu_organization = org
|
||||
if (project) newCredentials.zhipu_project = project
|
||||
else delete newCredentials.zhipu_project
|
||||
} else {
|
||||
delete newCredentials.zhipu_organization
|
||||
delete newCredentials.zhipu_project
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle API key
|
||||
|
||||
@@ -129,6 +129,22 @@ export default {
|
||||
responses: 'Responses',
|
||||
responsesDesc: 'Provider’s native Responses endpoint — ideal for Codex.',
|
||||
},
|
||||
zhipuTeam: {
|
||||
title: 'Team Plan Organization / Project ID',
|
||||
organization: 'Organization ID (team plan, optional)',
|
||||
organizationPlaceholder: 'Organization ID of the team Coding Plan',
|
||||
project: 'Project ID (team plan, optional)',
|
||||
projectPlaceholder: 'Project ID of the team Coding Plan',
|
||||
hint: 'Only required for the team GLM Coding Plan; when set, usage queries go through the team endpoint. Leave empty for personal plans. Click the question mark for how to obtain the IDs.',
|
||||
help: {
|
||||
title: 'How to get the Organization / Project ID',
|
||||
step1: 'Sign in to the Zhipu open platform (bigmodel.cn) with your team account and open "Coding Plan → Team → My Plan".',
|
||||
step2: 'Press F12 to open browser DevTools, switch to the Network tab, then reload the page.',
|
||||
step3: 'Type /api/biz/v1/organization into the Network filter box and click the matched request (e.g. api_keys).',
|
||||
step4: 'In the request URL, the org-… segment is the Organization ID and the proj_… segment is the Project ID (also visible as the bigmodel-organization / bigmodel-project request headers). Fill them into the fields above.',
|
||||
example: 'Example: …/organization/org-0610bE2D…/projects/proj_0798F20…/api_keys → org-0610bE2D… goes into "Organization ID", proj_0798F20… into "Project ID"',
|
||||
},
|
||||
},
|
||||
balance: 'Balance --',
|
||||
window5h: '5-hour window',
|
||||
windowWeekly: 'Weekly window',
|
||||
|
||||
@@ -332,6 +332,22 @@ export default {
|
||||
responses: 'Responses',
|
||||
responsesDesc: '供应商原生 Responses 端点,适配 Codex。',
|
||||
},
|
||||
zhipuTeam: {
|
||||
title: '团队版组织 / 项目 ID',
|
||||
organization: '组织 ID(团队版可选)',
|
||||
organizationPlaceholder: '团队版 Coding Plan 的组织 ID',
|
||||
project: '项目 ID(团队版可选)',
|
||||
projectPlaceholder: '团队版 Coding Plan 的项目 ID',
|
||||
hint: '仅团队版 GLM Coding Plan 需要填写,填写后用量查询走团队版端点;个人版留空即可。获取方式点击左侧问号查看教程。',
|
||||
help: {
|
||||
title: '如何获取组织 / 项目 ID',
|
||||
step1: '用团队版账号登录智谱开放平台(bigmodel.cn),进入「Coding Plan → 团队版 → 我的套餐」页面。',
|
||||
step2: '按 F12 打开浏览器开发者工具,切换到「Network / 网络」标签,然后刷新页面。',
|
||||
step3: '在 Network 的筛选框中输入 /api/biz/v1/organization,点击命中的请求(如 api_keys)。',
|
||||
step4: '请求 URL 中 org- 开头的一段即组织 ID、proj_ 开头的一段即项目 ID(也可在 Request Headers 中查看 bigmodel-organization / bigmodel-project 的值),分别填入上方输入框。',
|
||||
example: '示例:…/organization/org-0610bE2D…/projects/proj_0798F20…/api_keys → org-0610bE2D… 填「组织 ID」,proj_0798F20… 填「项目 ID」',
|
||||
},
|
||||
},
|
||||
balance: '余额 --',
|
||||
window5h: '5 小时窗口',
|
||||
windowWeekly: '每周窗口',
|
||||
|
||||
Reference in New Issue
Block a user