|
2 | 2 | <div v-if="isCN" class="space-y-3 rounded-lg border border-gray-200 p-3 dark:border-dark-600"> |
3 | 3 | <div class="grid gap-3 sm:grid-cols-2"> |
4 | 4 | <label class="text-sm font-medium text-gray-700 dark:text-gray-300">账号模式 |
5 | | - <select v-model="local.mode" class="input mt-1"><option value="payg">Pay-as-you-go</option><option value="coding">Coding Plan</option></select> |
| 5 | + <select v-model="local.mode" class="input mt-1"> |
| 6 | + <option value="payg">API key</option> |
| 7 | + <option value="coding">Coding Plan</option> |
| 8 | + </select> |
6 | 9 | </label> |
7 | 10 | <label class="text-sm font-medium text-gray-700 dark:text-gray-300">API 协议 |
8 | | - <select v-model="local.protocol" class="input mt-1"><option value="chat_completions">Chat Completions</option><option value="anthropic">Anthropic Messages</option><option v-if="supportsResponses" value="responses">Responses</option><option value="adaptive">Adaptive</option></select> |
| 11 | + <select v-model="local.protocol" class="input mt-1"> |
| 12 | + <option value="chat_completions">Chat Completions</option> |
| 13 | + <option value="anthropic">Anthropic Messages</option> |
| 14 | + <option v-if="supportsResponses" value="responses">Responses</option> |
| 15 | + <option value="adaptive">Adaptive</option> |
| 16 | + </select> |
9 | 17 | </label> |
10 | 18 | </div> |
| 19 | + <p class="text-xs text-gray-500 dark:text-gray-400"> |
| 20 | + 接口地址会根据平台、账号模式和 API 协议自动选择,并锁定为官方地址。 |
| 21 | + </p> |
11 | 22 | <template v-if="local.protocol === 'adaptive'"> |
12 | | - <label v-for="item in adaptiveItems" :key="item.key" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ item.label }} base URL |
13 | | - <input v-model="local.api_base_urls[item.key]" class="input mt-1" type="url" :placeholder="item.placeholder" /> |
14 | | - </label> |
| 23 | + <div v-for="item in adaptiveItems" :key="item.key" class="space-y-1"> |
| 24 | + <span class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ item.label }} 官方地址</span> |
| 25 | + <code v-if="isReadOnly" class="block rounded-md bg-gray-50 px-3 py-2 text-xs text-gray-600 break-all dark:bg-dark-700 dark:text-gray-300">{{ item.url }}</code> |
| 26 | + <input v-else v-model="local.api_base_urls[item.key]" class="input mt-1" type="url" :placeholder="item.url" /> |
| 27 | + </div> |
15 | 28 | </template> |
16 | | - <label v-else class="block text-sm font-medium text-gray-700 dark:text-gray-300">Base URL |
17 | | - <input v-model="local.base_url" class="input mt-1" type="url" :placeholder="defaultBaseUrl" /> |
18 | | - </label> |
| 29 | + <div v-else class="space-y-1"> |
| 30 | + <span class="block text-sm font-medium text-gray-700 dark:text-gray-300">官方接口地址</span> |
| 31 | + <code v-if="isReadOnly" class="block rounded-md bg-gray-50 px-3 py-2 text-xs text-gray-600 break-all dark:bg-dark-700 dark:text-gray-300">{{ defaultBaseUrl }}</code> |
| 32 | + <input v-else v-model="local.base_url" class="input mt-1" type="url" :placeholder="defaultBaseUrl" /> |
| 33 | + </div> |
19 | 34 | </div> |
20 | 35 | </template> |
21 | 36 | <script setup lang="ts"> |
22 | 37 | import { computed, reactive, watch } from 'vue' |
23 | 38 | import { defaultCNAdaptiveBaseUrls, defaultCNBaseUrl, cnSupportsNativeResponses, type CnAccountMode, type CnApiProtocol, type CnProviderPlatform } from './credentialsBuilder' |
24 | | -const props = defineProps<{ platform: string; modelValue: { mode: CnAccountMode; protocol: CnApiProtocol; base_url: string; api_base_urls: Record<string, string> } }>() |
| 39 | +const props = withDefaults(defineProps<{ platform: string; modelValue: { mode: CnAccountMode; protocol: CnApiProtocol; base_url: string; api_base_urls: Record<string, string> }; allowCustomBaseUrl?: boolean }>(), { |
| 40 | + allowCustomBaseUrl: false |
| 41 | +}) |
25 | 42 | const emit = defineEmits<{ (e: 'update:modelValue', value: typeof props.modelValue): void }>() |
26 | 43 | const isCN = computed(() => ['kimi', 'zhipu', 'deepseek', 'minimax', 'qwen'].includes(props.platform)) |
| 44 | +const isReadOnly = computed(() => props.allowCustomBaseUrl === false) |
| 45 | +const allowCustomBaseUrl = computed(() => !isReadOnly.value) |
27 | 46 | const supportsResponses = computed(() => cnSupportsNativeResponses(props.platform)) |
28 | 47 | const local = reactive({ mode: props.modelValue.mode, protocol: props.modelValue.protocol, base_url: props.modelValue.base_url, api_base_urls: { ...props.modelValue.api_base_urls } }) |
29 | 48 | const defaultBaseUrl = computed(() => isCN.value ? defaultCNBaseUrl(props.platform, local.mode, local.protocol) : '') |
30 | | -const adaptiveItems = computed(() => Object.entries(defaultCNAdaptiveBaseUrls(props.platform as CnProviderPlatform, local.mode)).filter(([key]) => key !== 'responses' || supportsResponses.value).map(([key, placeholder]) => ({ key, label: key === 'chat_completions' ? 'Chat Completions' : key[0].toUpperCase() + key.slice(1), placeholder }))) |
31 | | -watch(() => [local.mode, local.protocol] as const, ([mode, protocol], [previousMode, previousProtocol]) => { |
32 | | - const previousDefault = defaultCNBaseUrl(props.platform, previousMode, previousProtocol) |
33 | | - if (!local.base_url.trim() || local.base_url === previousDefault) { |
34 | | - local.base_url = defaultCNBaseUrl(props.platform, mode, protocol) |
| 49 | +const adaptiveItems = computed(() => Object.entries(defaultCNAdaptiveBaseUrls(props.platform as CnProviderPlatform, local.mode)) |
| 50 | + .filter(([key]) => key !== 'responses' || supportsResponses.value) |
| 51 | + .map(([key, url]) => ({ |
| 52 | + key, |
| 53 | + label: key === 'chat_completions' ? 'Chat Completions' : key === 'anthropic' ? 'Anthropic Messages' : 'Responses', |
| 54 | + url |
| 55 | + }))) |
| 56 | +
|
| 57 | +const sameConfig = (left: typeof props.modelValue, right: typeof props.modelValue) => |
| 58 | + left.mode === right.mode && |
| 59 | + left.protocol === right.protocol && |
| 60 | + left.base_url === right.base_url && |
| 61 | + JSON.stringify(left.api_base_urls) === JSON.stringify(right.api_base_urls) |
| 62 | +
|
| 63 | +const emitConfig = () => { |
| 64 | + const defaults = defaultCNAdaptiveBaseUrls(props.platform as CnProviderPlatform, local.mode) |
| 65 | + const isAdaptive = local.protocol === 'adaptive' |
| 66 | + const officialBaseUrl = isAdaptive ? defaults.chat_completions : defaultCNBaseUrl(props.platform, local.mode, local.protocol) |
| 67 | + const baseUrl = allowCustomBaseUrl.value ? (local.base_url.trim() || officialBaseUrl) : officialBaseUrl |
| 68 | + const apiBaseUrls = isAdaptive |
| 69 | + ? Object.fromEntries(Object.entries(defaults).map(([key, url]) => [key, allowCustomBaseUrl.value ? (local.api_base_urls[key]?.trim() || url) : url])) |
| 70 | + : {} |
| 71 | + const nextConfig = { |
| 72 | + mode: local.mode, |
| 73 | + protocol: local.protocol, |
| 74 | + base_url: baseUrl, |
| 75 | + api_base_urls: apiBaseUrls |
35 | 76 | } |
36 | | - const previousURLs = defaultCNAdaptiveBaseUrls(props.platform as CnProviderPlatform, previousMode) |
37 | | - const nextURLs = defaultCNAdaptiveBaseUrls(props.platform as CnProviderPlatform, mode) |
38 | | - for (const key of Object.keys(nextURLs) as Array<keyof typeof nextURLs>) { |
39 | | - if (!local.api_base_urls[key] || local.api_base_urls[key] === previousURLs[key]) { |
40 | | - local.api_base_urls[key] = nextURLs[key] |
41 | | - } |
| 77 | + if (local.base_url !== nextConfig.base_url) local.base_url = nextConfig.base_url |
| 78 | + if (JSON.stringify(local.api_base_urls) !== JSON.stringify(nextConfig.api_base_urls)) { |
| 79 | + local.api_base_urls = nextConfig.api_base_urls |
42 | 80 | } |
43 | | -}) |
44 | | -watch(local, () => emit('update:modelValue', { mode: local.mode, protocol: local.protocol, base_url: local.base_url, api_base_urls: { ...local.api_base_urls } }), { deep: true }) |
45 | | -const sameApiBaseUrls = (left: Record<string, string>, right: Record<string, string>) => { |
46 | | - const leftKeys = Object.keys(left) |
47 | | - const rightKeys = Object.keys(right) |
48 | | - if (leftKeys.length !== rightKeys.length) return false |
49 | | - return leftKeys.every((key) => left[key] === right[key]) |
| 81 | + if (!sameConfig(props.modelValue, nextConfig)) emit('update:modelValue', nextConfig) |
50 | 82 | } |
51 | | -watch(() => [props.platform, props.modelValue], () => { |
52 | | - const protocol = !cnSupportsNativeResponses(props.platform) && props.modelValue.protocol === 'responses' |
| 83 | +
|
| 84 | +watch(() => [local.mode, local.protocol, props.platform] as const, ([mode, protocol], previous) => { |
| 85 | + if (!cnSupportsNativeResponses(props.platform) && local.protocol === 'responses') { |
| 86 | + local.protocol = 'chat_completions' |
| 87 | + return |
| 88 | + } |
| 89 | + if (allowCustomBaseUrl.value && previous) { |
| 90 | + const previousDefault = defaultCNBaseUrl(previous[2], previous[0], previous[1]) |
| 91 | + if (!local.base_url.trim() || local.base_url === previousDefault) { |
| 92 | + local.base_url = defaultCNBaseUrl(props.platform, mode, protocol) |
| 93 | + } |
| 94 | + const previousURLs = defaultCNAdaptiveBaseUrls(previous[2] as CnProviderPlatform, previous[0]) |
| 95 | + const nextURLs = defaultCNAdaptiveBaseUrls(props.platform as CnProviderPlatform, mode) |
| 96 | + for (const key of Object.keys(nextURLs) as Array<keyof typeof nextURLs>) { |
| 97 | + if (!local.api_base_urls[key] || local.api_base_urls[key] === previousURLs[key]) { |
| 98 | + local.api_base_urls[key] = nextURLs[key] |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + emitConfig() |
| 103 | +}, { immediate: true }) |
| 104 | +
|
| 105 | +watch(local, () => emitConfig(), { deep: true }) |
| 106 | +
|
| 107 | +watch(() => props.modelValue, (value) => { |
| 108 | + if (local.mode !== value.mode) local.mode = value.mode |
| 109 | + const protocol = !cnSupportsNativeResponses(props.platform) && value.protocol === 'responses' |
53 | 110 | ? 'chat_completions' |
54 | | - : props.modelValue.protocol |
55 | | - if (local.mode !== props.modelValue.mode) local.mode = props.modelValue.mode |
| 111 | + : value.protocol |
56 | 112 | if (local.protocol !== protocol) local.protocol = protocol |
57 | | - if (local.base_url !== props.modelValue.base_url) local.base_url = props.modelValue.base_url |
58 | | - if (!sameApiBaseUrls(local.api_base_urls, props.modelValue.api_base_urls)) { |
59 | | - local.api_base_urls = { ...props.modelValue.api_base_urls } |
| 113 | + if (allowCustomBaseUrl.value) { |
| 114 | + if (local.base_url !== value.base_url) local.base_url = value.base_url |
| 115 | + if (JSON.stringify(local.api_base_urls) !== JSON.stringify(value.api_base_urls)) local.api_base_urls = { ...value.api_base_urls } |
60 | 116 | } |
61 | | -}, { deep: true, immediate: true }) |
| 117 | + if (local.protocol === protocol && local.mode === value.mode) emitConfig() |
| 118 | +}, { deep: true }) |
62 | 119 | </script> |
0 commit comments