feat(keys): 适配 Antigravity 和 Gemini 平台的使用教程与 CCS 导入
- UseKeyModal: 添加 Antigravity 两级 Tab (Claude Code / Gemini CLI) - UseKeyModal: 添加 Gemini 平台的 Gemini CLI 教程 - UseKeyModal: Antigravity 平台统一使用 /antigravity 后缀 - KeysView: CCS 导入支持 Antigravity (询问客户端) / Gemini / OpenAI - i18n: 添加相关中英文翻译
This commit is contained in:
@@ -28,7 +28,29 @@
|
|||||||
{{ platformDescription }}
|
{{ platformDescription }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- OS Tabs -->
|
<!-- Client Tabs (only for Antigravity platform) -->
|
||||||
|
<div v-if="platform === 'antigravity'" class="border-b border-gray-200 dark:border-dark-700">
|
||||||
|
<nav class="-mb-px flex space-x-6" aria-label="Client">
|
||||||
|
<button
|
||||||
|
v-for="tab in clientTabs"
|
||||||
|
:key="tab.id"
|
||||||
|
@click="activeClientTab = tab.id"
|
||||||
|
:class="[
|
||||||
|
'whitespace-nowrap py-2.5 px-1 border-b-2 font-medium text-sm transition-colors',
|
||||||
|
activeClientTab === tab.id
|
||||||
|
? 'border-primary-500 text-primary-600 dark:text-primary-400'
|
||||||
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<component :is="tab.icon" class="w-4 h-4" />
|
||||||
|
{{ tab.label }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- OS/Shell Tabs -->
|
||||||
<div class="border-b border-gray-200 dark:border-dark-700">
|
<div class="border-b border-gray-200 dark:border-dark-700">
|
||||||
<nav class="-mb-px flex space-x-4" aria-label="Tabs">
|
<nav class="-mb-px flex space-x-4" aria-label="Tabs">
|
||||||
<button
|
<button
|
||||||
@@ -154,16 +176,21 @@ const { copyToClipboard: clipboardCopy } = useClipboard()
|
|||||||
|
|
||||||
const copiedIndex = ref<number | null>(null)
|
const copiedIndex = ref<number | null>(null)
|
||||||
const activeTab = ref<string>('unix')
|
const activeTab = ref<string>('unix')
|
||||||
|
const activeClientTab = ref<string>('claude') // Level 1 tab for antigravity platform
|
||||||
|
|
||||||
// Reset active tab when platform changes
|
// Reset tabs when platform changes
|
||||||
watch(() => props.platform, (newPlatform) => {
|
watch(() => props.platform, (newPlatform) => {
|
||||||
if (newPlatform === 'openai') {
|
activeTab.value = 'unix'
|
||||||
activeTab.value = 'unix'
|
if (newPlatform === 'antigravity') {
|
||||||
} else {
|
activeClientTab.value = 'claude'
|
||||||
activeTab.value = 'unix'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Reset shell tab when client changes (for antigravity)
|
||||||
|
watch(activeClientTab, () => {
|
||||||
|
activeTab.value = 'unix'
|
||||||
|
})
|
||||||
|
|
||||||
// Icon components
|
// Icon components
|
||||||
const AppleIcon = {
|
const AppleIcon = {
|
||||||
render() {
|
render() {
|
||||||
@@ -189,8 +216,52 @@ const WindowsIcon = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anthropic tabs (3 shell types)
|
// Terminal icon for Claude Code
|
||||||
const anthropicTabs: TabConfig[] = [
|
const TerminalIcon = {
|
||||||
|
render() {
|
||||||
|
return h('svg', {
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
'stroke-width': '1.5',
|
||||||
|
class: 'w-4 h-4'
|
||||||
|
}, [
|
||||||
|
h('path', {
|
||||||
|
'stroke-linecap': 'round',
|
||||||
|
'stroke-linejoin': 'round',
|
||||||
|
d: 'm6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 17.25V6.75A2.25 2.25 0 0 0 18.75 4.5H5.25A2.25 2.25 0 0 0 3 6.75v10.5A2.25 2.25 0 0 0 5.25 20.25Z'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sparkle icon for Gemini
|
||||||
|
const SparkleIcon = {
|
||||||
|
render() {
|
||||||
|
return h('svg', {
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
'stroke-width': '1.5',
|
||||||
|
class: 'w-4 h-4'
|
||||||
|
}, [
|
||||||
|
h('path', {
|
||||||
|
'stroke-linecap': 'round',
|
||||||
|
'stroke-linejoin': 'round',
|
||||||
|
d: 'M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client tabs for Antigravity platform (Level 1)
|
||||||
|
const clientTabs = computed((): TabConfig[] => [
|
||||||
|
{ id: 'claude', label: t('keys.useKeyModal.antigravity.claudeCode'), icon: TerminalIcon },
|
||||||
|
{ id: 'gemini', label: t('keys.useKeyModal.antigravity.geminiCli'), icon: SparkleIcon }
|
||||||
|
])
|
||||||
|
|
||||||
|
// Shell tabs (3 types for environment variable based configs)
|
||||||
|
const shellTabs: TabConfig[] = [
|
||||||
{ id: 'unix', label: 'macOS / Linux', icon: AppleIcon },
|
{ id: 'unix', label: 'macOS / Linux', icon: AppleIcon },
|
||||||
{ id: 'cmd', label: 'Windows CMD', icon: WindowsIcon },
|
{ id: 'cmd', label: 'Windows CMD', icon: WindowsIcon },
|
||||||
{ id: 'powershell', label: 'PowerShell', icon: WindowsIcon }
|
{ id: 'powershell', label: 'PowerShell', icon: WindowsIcon }
|
||||||
@@ -204,26 +275,40 @@ const openaiTabs: TabConfig[] = [
|
|||||||
|
|
||||||
const currentTabs = computed(() => {
|
const currentTabs = computed(() => {
|
||||||
if (props.platform === 'openai') {
|
if (props.platform === 'openai') {
|
||||||
return openaiTabs
|
return openaiTabs // 2 tabs: unix, windows
|
||||||
}
|
}
|
||||||
return anthropicTabs
|
// All other platforms (anthropic, gemini, antigravity) use shell tabs
|
||||||
|
return shellTabs
|
||||||
})
|
})
|
||||||
|
|
||||||
const platformDescription = computed(() => {
|
const platformDescription = computed(() => {
|
||||||
if (props.platform === 'openai') {
|
switch (props.platform) {
|
||||||
return t('keys.useKeyModal.openai.description')
|
case 'openai':
|
||||||
|
return t('keys.useKeyModal.openai.description')
|
||||||
|
case 'gemini':
|
||||||
|
return t('keys.useKeyModal.gemini.description')
|
||||||
|
case 'antigravity':
|
||||||
|
return t('keys.useKeyModal.antigravity.description')
|
||||||
|
default:
|
||||||
|
return t('keys.useKeyModal.description')
|
||||||
}
|
}
|
||||||
return t('keys.useKeyModal.description')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const platformNote = computed(() => {
|
const platformNote = computed(() => {
|
||||||
if (props.platform === 'openai') {
|
switch (props.platform) {
|
||||||
if (activeTab.value === 'windows') {
|
case 'openai':
|
||||||
return t('keys.useKeyModal.openai.noteWindows')
|
return activeTab.value === 'windows'
|
||||||
}
|
? t('keys.useKeyModal.openai.noteWindows')
|
||||||
return t('keys.useKeyModal.openai.note')
|
: t('keys.useKeyModal.openai.note')
|
||||||
|
case 'gemini':
|
||||||
|
return t('keys.useKeyModal.gemini.note')
|
||||||
|
case 'antigravity':
|
||||||
|
return activeClientTab.value === 'claude'
|
||||||
|
? t('keys.useKeyModal.antigravity.claudeNote')
|
||||||
|
: t('keys.useKeyModal.antigravity.geminiNote')
|
||||||
|
default:
|
||||||
|
return t('keys.useKeyModal.note')
|
||||||
}
|
}
|
||||||
return t('keys.useKeyModal.note')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Syntax highlighting helpers
|
// Syntax highlighting helpers
|
||||||
@@ -239,11 +324,20 @@ const currentFiles = computed((): FileConfig[] => {
|
|||||||
const baseUrl = props.baseUrl || window.location.origin
|
const baseUrl = props.baseUrl || window.location.origin
|
||||||
const apiKey = props.apiKey
|
const apiKey = props.apiKey
|
||||||
|
|
||||||
if (props.platform === 'openai') {
|
switch (props.platform) {
|
||||||
return generateOpenAIFiles(baseUrl, apiKey)
|
case 'openai':
|
||||||
|
return generateOpenAIFiles(baseUrl, apiKey)
|
||||||
|
case 'gemini':
|
||||||
|
return [generateGeminiCliContent(baseUrl, apiKey)]
|
||||||
|
case 'antigravity':
|
||||||
|
// Both Claude Code and Gemini CLI need /antigravity suffix for antigravity platform
|
||||||
|
if (activeClientTab.value === 'claude') {
|
||||||
|
return generateAnthropicFiles(`${baseUrl}/antigravity`, apiKey)
|
||||||
|
}
|
||||||
|
return [generateGeminiCliContent(`${baseUrl}/antigravity`, apiKey)]
|
||||||
|
default: // anthropic
|
||||||
|
return generateAnthropicFiles(baseUrl, apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateAnthropicFiles(baseUrl, apiKey)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function generateAnthropicFiles(baseUrl: string, apiKey: string): FileConfig[] {
|
function generateAnthropicFiles(baseUrl: string, apiKey: string): FileConfig[] {
|
||||||
@@ -282,6 +376,51 @@ ${keyword('$env:')}${variable('ANTHROPIC_AUTH_TOKEN')}${operator('=')}${string(`
|
|||||||
return [{ path, content, highlighted }]
|
return [{ path, content, highlighted }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateGeminiCliContent(baseUrl: string, apiKey: string): FileConfig {
|
||||||
|
const model = 'gemini-2.5-pro'
|
||||||
|
const modelComment = t('keys.useKeyModal.gemini.modelComment')
|
||||||
|
let path: string
|
||||||
|
let content: string
|
||||||
|
let highlighted: string
|
||||||
|
|
||||||
|
switch (activeTab.value) {
|
||||||
|
case 'unix':
|
||||||
|
path = 'Terminal'
|
||||||
|
content = `export GOOGLE_GEMINI_BASE_URL="${baseUrl}"
|
||||||
|
export GEMINI_API_KEY="${apiKey}"
|
||||||
|
export GEMINI_MODEL="${model}" # ${modelComment}`
|
||||||
|
highlighted = `${keyword('export')} ${variable('GOOGLE_GEMINI_BASE_URL')}${operator('=')}${string(`"${baseUrl}"`)}
|
||||||
|
${keyword('export')} ${variable('GEMINI_API_KEY')}${operator('=')}${string(`"${apiKey}"`)}
|
||||||
|
${keyword('export')} ${variable('GEMINI_MODEL')}${operator('=')}${string(`"${model}"`)} ${comment(`# ${modelComment}`)}`
|
||||||
|
break
|
||||||
|
case 'cmd':
|
||||||
|
path = 'Command Prompt'
|
||||||
|
content = `set GOOGLE_GEMINI_BASE_URL=${baseUrl}
|
||||||
|
set GEMINI_API_KEY=${apiKey}
|
||||||
|
set GEMINI_MODEL=${model}`
|
||||||
|
highlighted = `${keyword('set')} ${variable('GOOGLE_GEMINI_BASE_URL')}${operator('=')}${baseUrl}
|
||||||
|
${keyword('set')} ${variable('GEMINI_API_KEY')}${operator('=')}${apiKey}
|
||||||
|
${keyword('set')} ${variable('GEMINI_MODEL')}${operator('=')}${model}
|
||||||
|
${comment(`REM ${modelComment}`)}`
|
||||||
|
break
|
||||||
|
case 'powershell':
|
||||||
|
path = 'PowerShell'
|
||||||
|
content = `$env:GOOGLE_GEMINI_BASE_URL="${baseUrl}"
|
||||||
|
$env:GEMINI_API_KEY="${apiKey}"
|
||||||
|
$env:GEMINI_MODEL="${model}" # ${modelComment}`
|
||||||
|
highlighted = `${keyword('$env:')}${variable('GOOGLE_GEMINI_BASE_URL')}${operator('=')}${string(`"${baseUrl}"`)}
|
||||||
|
${keyword('$env:')}${variable('GEMINI_API_KEY')}${operator('=')}${string(`"${apiKey}"`)}
|
||||||
|
${keyword('$env:')}${variable('GEMINI_MODEL')}${operator('=')}${string(`"${model}"`)} ${comment(`# ${modelComment}`)}`
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
path = 'Terminal'
|
||||||
|
content = ''
|
||||||
|
highlighted = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return { path, content, highlighted }
|
||||||
|
}
|
||||||
|
|
||||||
function generateOpenAIFiles(baseUrl: string, apiKey: string): FileConfig[] {
|
function generateOpenAIFiles(baseUrl: string, apiKey: string): FileConfig[] {
|
||||||
const isWindows = activeTab.value === 'windows'
|
const isWindows = activeTab.value === 'windows'
|
||||||
const configDir = isWindows ? '%userprofile%\\.codex' : '~/.codex'
|
const configDir = isWindows ? '%userprofile%\\.codex' : '~/.codex'
|
||||||
|
|||||||
@@ -321,6 +321,18 @@ export default {
|
|||||||
note: 'Make sure the config directory exists. macOS/Linux users can run mkdir -p ~/.codex to create it.',
|
note: 'Make sure the config directory exists. macOS/Linux users can run mkdir -p ~/.codex to create it.',
|
||||||
noteWindows: 'Press Win+R and enter %userprofile%\\.codex to open the config directory. Create it manually if it does not exist.',
|
noteWindows: 'Press Win+R and enter %userprofile%\\.codex to open the config directory. Create it manually if it does not exist.',
|
||||||
},
|
},
|
||||||
|
antigravity: {
|
||||||
|
description: 'Configure API access for Antigravity group. Select the configuration method based on your client.',
|
||||||
|
claudeCode: 'Claude Code',
|
||||||
|
geminiCli: 'Gemini CLI',
|
||||||
|
claudeNote: 'These environment variables will be active in the current terminal session. For permanent configuration, add them to ~/.bashrc, ~/.zshrc, or the appropriate configuration file.',
|
||||||
|
geminiNote: 'These environment variables will be active in the current terminal session. For permanent configuration, add them to ~/.bashrc, ~/.zshrc, or the appropriate configuration file.',
|
||||||
|
},
|
||||||
|
gemini: {
|
||||||
|
description: 'Add the following environment variables to your terminal profile or run directly in terminal to configure Gemini CLI access.',
|
||||||
|
modelComment: 'If you have Gemini 3 access, you can use: gemini-3-pro-preview',
|
||||||
|
note: 'These environment variables will be active in the current terminal session. For permanent configuration, add them to ~/.bashrc, ~/.zshrc, or the appropriate configuration file.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
customKeyLabel: 'Custom Key',
|
customKeyLabel: 'Custom Key',
|
||||||
customKeyPlaceholder: 'Enter your custom key (min 16 chars)',
|
customKeyPlaceholder: 'Enter your custom key (min 16 chars)',
|
||||||
@@ -328,7 +340,15 @@ export default {
|
|||||||
customKeyTooShort: 'Custom key must be at least 16 characters',
|
customKeyTooShort: 'Custom key must be at least 16 characters',
|
||||||
customKeyInvalidChars: 'Custom key can only contain letters, numbers, underscores, and hyphens',
|
customKeyInvalidChars: 'Custom key can only contain letters, numbers, underscores, and hyphens',
|
||||||
customKeyRequired: 'Please enter a custom key',
|
customKeyRequired: 'Please enter a custom key',
|
||||||
ccSwitchNotInstalled: 'CC-Switch is not installed or the protocol handler is not registered. Please install CC-Switch first or manually copy the API key.'
|
ccSwitchNotInstalled: 'CC-Switch is not installed or the protocol handler is not registered. Please install CC-Switch first or manually copy the API key.',
|
||||||
|
ccsClientSelect: {
|
||||||
|
title: 'Select Client',
|
||||||
|
description: 'Please select the client type to import to CC-Switch:',
|
||||||
|
claudeCode: 'Claude Code',
|
||||||
|
claudeCodeDesc: 'Import as Claude Code configuration',
|
||||||
|
geminiCli: 'Gemini CLI',
|
||||||
|
geminiCliDesc: 'Import as Gemini CLI configuration',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
|
|||||||
@@ -317,6 +317,18 @@ export default {
|
|||||||
note: '请确保配置目录存在。macOS/Linux 用户可运行 mkdir -p ~/.codex 创建目录。',
|
note: '请确保配置目录存在。macOS/Linux 用户可运行 mkdir -p ~/.codex 创建目录。',
|
||||||
noteWindows: '按 Win+R,输入 %userprofile%\\.codex 打开配置目录。如目录不存在,请先手动创建。',
|
noteWindows: '按 Win+R,输入 %userprofile%\\.codex 打开配置目录。如目录不存在,请先手动创建。',
|
||||||
},
|
},
|
||||||
|
antigravity: {
|
||||||
|
description: '为 Antigravity 分组配置 API 访问。请根据您使用的客户端选择对应的配置方式。',
|
||||||
|
claudeCode: 'Claude Code',
|
||||||
|
geminiCli: 'Gemini CLI',
|
||||||
|
claudeNote: '这些环境变量将在当前终端会话中生效。如需永久配置,请将其添加到 ~/.bashrc、~/.zshrc 或相应的配置文件中。',
|
||||||
|
geminiNote: '这些环境变量将在当前终端会话中生效。如需永久配置,请将其添加到 ~/.bashrc、~/.zshrc 或相应的配置文件中。',
|
||||||
|
},
|
||||||
|
gemini: {
|
||||||
|
description: '将以下环境变量添加到您的终端配置文件或直接在终端中运行,以配置 Gemini CLI 访问。',
|
||||||
|
modelComment: '如果你有 Gemini 3 权限可以填:gemini-3-pro-preview',
|
||||||
|
note: '这些环境变量将在当前终端会话中生效。如需永久配置,请将其添加到 ~/.bashrc、~/.zshrc 或相应的配置文件中。',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
customKeyLabel: '自定义密钥',
|
customKeyLabel: '自定义密钥',
|
||||||
customKeyPlaceholder: '输入自定义密钥(至少16个字符)',
|
customKeyPlaceholder: '输入自定义密钥(至少16个字符)',
|
||||||
@@ -324,7 +336,15 @@ export default {
|
|||||||
customKeyTooShort: '自定义密钥至少需要16个字符',
|
customKeyTooShort: '自定义密钥至少需要16个字符',
|
||||||
customKeyInvalidChars: '自定义密钥只能包含字母、数字、下划线和连字符',
|
customKeyInvalidChars: '自定义密钥只能包含字母、数字、下划线和连字符',
|
||||||
customKeyRequired: '请输入自定义密钥',
|
customKeyRequired: '请输入自定义密钥',
|
||||||
ccSwitchNotInstalled: 'CC-Switch 未安装或协议处理程序未注册。请先安装 CC-Switch 或手动复制 API 密钥。'
|
ccSwitchNotInstalled: 'CC-Switch 未安装或协议处理程序未注册。请先安装 CC-Switch 或手动复制 API 密钥。',
|
||||||
|
ccsClientSelect: {
|
||||||
|
title: '选择客户端',
|
||||||
|
description: '请选择您要导入到 CC-Switch 的客户端类型:',
|
||||||
|
claudeCode: 'Claude Code',
|
||||||
|
claudeCodeDesc: '导入为 Claude Code 配置',
|
||||||
|
geminiCli: 'Gemini CLI',
|
||||||
|
geminiCliDesc: '导入为 Gemini CLI 配置',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
|
|||||||
@@ -173,7 +173,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<!-- Import to CC Switch Button -->
|
<!-- Import to CC Switch Button -->
|
||||||
<button
|
<button
|
||||||
@click="importToCcswitch(row.key)"
|
@click="importToCcswitch(row)"
|
||||||
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400"
|
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -453,6 +453,49 @@
|
|||||||
@close="closeUseKeyModal"
|
@close="closeUseKeyModal"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- CCS Client Selection Dialog for Antigravity -->
|
||||||
|
<BaseDialog
|
||||||
|
:show="showCcsClientSelect"
|
||||||
|
:title="t('keys.ccsClientSelect.title')"
|
||||||
|
width="narrow"
|
||||||
|
@close="closeCcsClientSelect"
|
||||||
|
>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ t('keys.ccsClientSelect.description') }}
|
||||||
|
</p>
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<button
|
||||||
|
@click="handleCcsClientSelect('claude')"
|
||||||
|
class="flex flex-col items-center gap-2 p-4 rounded-xl border-2 border-gray-200 dark:border-dark-600 hover:border-primary-500 dark:hover:border-primary-500 hover:bg-primary-50 dark:hover:bg-primary-900/20 transition-all"
|
||||||
|
>
|
||||||
|
<svg class="w-8 h-8 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 17.25V6.75A2.25 2.25 0 0 0 18.75 4.5H5.25A2.25 2.25 0 0 0 3 6.75v10.5A2.25 2.25 0 0 0 5.25 20.25Z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium text-gray-900 dark:text-white">{{ t('keys.ccsClientSelect.claudeCode') }}</span>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('keys.ccsClientSelect.claudeCodeDesc') }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="handleCcsClientSelect('gemini')"
|
||||||
|
class="flex flex-col items-center gap-2 p-4 rounded-xl border-2 border-gray-200 dark:border-dark-600 hover:border-primary-500 dark:hover:border-primary-500 hover:bg-primary-50 dark:hover:bg-primary-900/20 transition-all"
|
||||||
|
>
|
||||||
|
<svg class="w-8 h-8 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium text-gray-900 dark:text-white">{{ t('keys.ccsClientSelect.geminiCli') }}</span>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('keys.ccsClientSelect.geminiCliDesc') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<button @click="closeCcsClientSelect" class="btn btn-secondary">
|
||||||
|
{{ t('common.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</BaseDialog>
|
||||||
|
|
||||||
<!-- Group Selector Dropdown (Teleported to body to avoid overflow clipping) -->
|
<!-- Group Selector Dropdown (Teleported to body to avoid overflow clipping) -->
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div
|
<div
|
||||||
@@ -563,6 +606,8 @@ const showCreateModal = ref(false)
|
|||||||
const showEditModal = ref(false)
|
const showEditModal = ref(false)
|
||||||
const showDeleteDialog = ref(false)
|
const showDeleteDialog = ref(false)
|
||||||
const showUseKeyModal = ref(false)
|
const showUseKeyModal = ref(false)
|
||||||
|
const showCcsClientSelect = ref(false)
|
||||||
|
const pendingCcsRow = ref<ApiKey | null>(null)
|
||||||
const selectedKey = ref<ApiKey | null>(null)
|
const selectedKey = ref<ApiKey | null>(null)
|
||||||
const copiedKeyId = ref<number | null>(null)
|
const copiedKeyId = ref<number | null>(null)
|
||||||
const groupSelectorKeyId = ref<number | null>(null)
|
const groupSelectorKeyId = ref<number | null>(null)
|
||||||
@@ -871,8 +916,48 @@ const closeModals = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const importToCcswitch = (apiKey: string) => {
|
const importToCcswitch = (row: ApiKey) => {
|
||||||
|
const platform = row.group?.platform || 'anthropic'
|
||||||
|
|
||||||
|
// For antigravity platform, show client selection dialog
|
||||||
|
if (platform === 'antigravity') {
|
||||||
|
pendingCcsRow.value = row
|
||||||
|
showCcsClientSelect.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other platforms, execute directly
|
||||||
|
executeCcsImport(row, platform === 'gemini' ? 'gemini' : 'claude')
|
||||||
|
}
|
||||||
|
|
||||||
|
const executeCcsImport = (row: ApiKey, clientType: 'claude' | 'gemini') => {
|
||||||
const baseUrl = publicSettings.value?.api_base_url || window.location.origin
|
const baseUrl = publicSettings.value?.api_base_url || window.location.origin
|
||||||
|
const platform = row.group?.platform || 'anthropic'
|
||||||
|
|
||||||
|
// Determine app name and endpoint based on platform and client type
|
||||||
|
let app: string
|
||||||
|
let endpoint: string
|
||||||
|
|
||||||
|
if (platform === 'antigravity') {
|
||||||
|
// Antigravity always uses /antigravity suffix
|
||||||
|
app = clientType === 'gemini' ? 'gemini' : 'claude'
|
||||||
|
endpoint = `${baseUrl}/antigravity`
|
||||||
|
} else {
|
||||||
|
switch (platform) {
|
||||||
|
case 'openai':
|
||||||
|
app = 'codex'
|
||||||
|
endpoint = baseUrl
|
||||||
|
break
|
||||||
|
case 'gemini':
|
||||||
|
app = 'gemini'
|
||||||
|
endpoint = baseUrl
|
||||||
|
break
|
||||||
|
default: // anthropic
|
||||||
|
app = 'claude'
|
||||||
|
endpoint = baseUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const usageScript = `({
|
const usageScript = `({
|
||||||
request: {
|
request: {
|
||||||
url: "{{baseUrl}}/v1/usage",
|
url: "{{baseUrl}}/v1/usage",
|
||||||
@@ -889,11 +974,11 @@ const importToCcswitch = (apiKey: string) => {
|
|||||||
})`
|
})`
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
resource: 'provider',
|
resource: 'provider',
|
||||||
app: 'claude',
|
app: app,
|
||||||
name: 'sub2api',
|
name: 'sub2api',
|
||||||
homepage: baseUrl,
|
homepage: baseUrl,
|
||||||
endpoint: baseUrl,
|
endpoint: endpoint,
|
||||||
apiKey: apiKey,
|
apiKey: row.key,
|
||||||
configFormat: 'json',
|
configFormat: 'json',
|
||||||
usageEnabled: 'true',
|
usageEnabled: 'true',
|
||||||
usageScript: btoa(usageScript),
|
usageScript: btoa(usageScript),
|
||||||
@@ -916,6 +1001,19 @@ const importToCcswitch = (apiKey: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCcsClientSelect = (clientType: 'claude' | 'gemini') => {
|
||||||
|
if (pendingCcsRow.value) {
|
||||||
|
executeCcsImport(pendingCcsRow.value, clientType)
|
||||||
|
}
|
||||||
|
showCcsClientSelect.value = false
|
||||||
|
pendingCcsRow.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeCcsClientSelect = () => {
|
||||||
|
showCcsClientSelect.value = false
|
||||||
|
pendingCcsRow.value = null
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadApiKeys()
|
loadApiKeys()
|
||||||
loadGroups()
|
loadGroups()
|
||||||
|
|||||||
Reference in New Issue
Block a user