feat(ai): add Jalapeno Cloud partner preset
This commit is contained in:
16 files changed
+253
-47
No files matched your search
@@ -45,7 +45,7 @@ import { Input } from "@/components/ui/input";
|
||||
import PasswordInput from "@/components/ui/PasswordInput.vue";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { SearchableSelect } from "@/components/ui/searchable-select";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
@@ -53,7 +53,7 @@ import { HelpTooltip, Tooltip, TooltipContent, TooltipTrigger, TooltipProvider }
|
||||
import {
|
||||
useSettingsStore,
|
||||
AI_PROVIDER_PRESETS,
|
||||
aiProviderLabel,
|
||||
AI_PROVIDER_PARTNER_PRESETS,
|
||||
EDITOR_THEMES,
|
||||
DEFAULT_EDITOR_SETTINGS,
|
||||
DEFAULT_DESKTOP_SETTINGS,
|
||||
@@ -63,6 +63,10 @@ import {
|
||||
normalizeDuckDbWorkerMaxProcesses,
|
||||
normalizeAiEnv,
|
||||
normalizeAiHeaders,
|
||||
getAiProviderPreset,
|
||||
getAiProviderPresetId,
|
||||
getAiProviderPresetOption,
|
||||
isAiPartnerProviderPreset,
|
||||
type AiProvider,
|
||||
type AiApiStyle,
|
||||
type AiAuthMethod,
|
||||
@@ -3381,10 +3385,11 @@ const aiDeleteConfigId = ref<string | null>(null);
|
||||
const CLI_AI_PROVIDERS = new Set<AiProvider>(["claude-code-cli", "codex-cli", "opencode-cli", "pi-agent-cli", "cursor-cli", "grok-cli", "codebuddy-cli", "qoder-cli"]);
|
||||
const OPENCODE_CONTROL_ENV = new Set(["OPENCODE_CONFIG", "OPENCODE_CONFIG_CONTENT", "OPENCODE_CONFIG_DIR", "OPENCODE_DB", "OPENCODE_PERMISSION", "OPENCODE_DISABLE_PROJECT_CONFIG"]);
|
||||
const CURSOR_CONTROL_ENV = new Set(["CURSOR_CONFIG_DIR", "CURSOR_DATA_DIR"]);
|
||||
const aiProviderOptions = computed(() => Object.values(AI_PROVIDER_PRESETS).filter((provider) => !isWeb || !CLI_AI_PROVIDERS.has(provider.provider)));
|
||||
const selectedAiProviderPreset = computed(() => AI_PROVIDER_PRESETS[aiEditProvider.value]);
|
||||
const builtinAiProviderOptions = computed(() => Object.values(AI_PROVIDER_PRESETS).filter((provider) => !isWeb || !CLI_AI_PROVIDERS.has(provider.provider)));
|
||||
const partnerAiProviderOptions = computed(() => AI_PROVIDER_PARTNER_PRESETS.filter((provider) => !isWeb || !CLI_AI_PROVIDERS.has(provider.provider)));
|
||||
|
||||
const aiEditProvider = ref<AiProvider>("claude");
|
||||
const aiEditProviderPresetId = ref("claude");
|
||||
const aiEditApiKey = ref("");
|
||||
const aiEditAuthMethod = ref<AiAuthMethod>("api-key");
|
||||
const aiEditEndpoint = ref("");
|
||||
@@ -3415,6 +3420,8 @@ const aiEditQoderCliPath = ref("");
|
||||
const aiEditQoderCliEnvRows = ref<AiEnvRow[]>([]);
|
||||
|
||||
const aiAnthropicMessagesMode = computed(() => aiEditApiStyle.value === "anthropic-messages");
|
||||
const selectedAiProviderPreset = computed(() => getAiProviderPresetOption(aiEditProviderPresetId.value));
|
||||
const selectedAiPartnerPreset = computed(() => (isAiPartnerProviderPreset(selectedAiProviderPreset.value) ? selectedAiProviderPreset.value : null));
|
||||
|
||||
const aiTesting = ref(false);
|
||||
const aiTestResult = ref<"" | "success" | "error">("");
|
||||
@@ -3515,7 +3522,7 @@ const aiEditCliEnvRows = computed(() => {
|
||||
watch(aiIsCliProvider, (isCliProvider) => {
|
||||
if (isCliProvider) void ensureCliMcpStatus();
|
||||
});
|
||||
const aiRequiresApiKey = computed(() => AI_PROVIDER_PRESETS[aiEditProvider.value].requiresApiKey);
|
||||
const aiRequiresApiKey = computed(() => selectedAiProviderPreset.value.requiresApiKey);
|
||||
const aiUsesConfigurableAnthropicAuth = computed(() => aiEditProvider.value === "claude" || aiEditProvider.value === "anthropic-compatible" || (aiEditProvider.value === "custom" && aiAnthropicMessagesMode.value));
|
||||
const aiUsesCompatibleAnthropicApi = computed(() => aiEditProvider.value === "anthropic-compatible" || (aiEditProvider.value === "custom" && aiAnthropicMessagesMode.value));
|
||||
const aiSupportsAuthMethod = computed(() => aiUsesConfigurableAnthropicAuth.value);
|
||||
@@ -3707,18 +3714,20 @@ function syncAiEditState() {
|
||||
aiTestErrorCopied.value = false;
|
||||
}
|
||||
|
||||
function aiSelectProvider(provider: AiProvider) {
|
||||
function aiSelectProvider(presetId: string) {
|
||||
const preset = getAiProviderPresetOption(presetId);
|
||||
const provider = preset.provider;
|
||||
if (isWeb && CLI_AI_PROVIDERS.has(provider)) return;
|
||||
if (provider === aiEditProvider.value) return;
|
||||
if (presetId === aiEditProviderPresetId.value) return;
|
||||
|
||||
// Apply new provider's preset defaults to edit state
|
||||
const preset = AI_PROVIDER_PRESETS[provider];
|
||||
aiEditProviderPresetId.value = presetId;
|
||||
aiEditProvider.value = provider;
|
||||
aiEditApiKey.value = "";
|
||||
aiEditAuthMethod.value = preset.authMethod;
|
||||
aiEditEndpoint.value = preset.endpoint;
|
||||
aiEditModel.value = "";
|
||||
aiEditLegacyModels.value = [];
|
||||
aiEditModel.value = preset.group === "partner" ? preset.model : "";
|
||||
aiEditLegacyModels.value = preset.group === "partner" ? [...(preset.models ?? [])] : [];
|
||||
aiEditApiStyle.value = preset.apiStyle;
|
||||
aiEditCustomHeaderRows.value = [];
|
||||
aiEditEnableThinking.value = true;
|
||||
@@ -3747,6 +3756,7 @@ function aiEnterEditMode(configId?: string) {
|
||||
if (config) {
|
||||
aiEditConfigName.value = config.name;
|
||||
aiEditProvider.value = config.provider;
|
||||
aiEditProviderPresetId.value = getAiProviderPresetId(config.provider, config.endpoint);
|
||||
aiEditApiKey.value = config.apiKey;
|
||||
aiEditAuthMethod.value = config.authMethod;
|
||||
aiEditEndpoint.value = config.endpoint;
|
||||
@@ -3782,6 +3792,7 @@ function aiEnterEditMode(configId?: string) {
|
||||
} else {
|
||||
aiEditConfigName.value = "";
|
||||
aiEditProvider.value = "claude";
|
||||
aiEditProviderPresetId.value = "claude";
|
||||
aiEditApiKey.value = "";
|
||||
aiEditAuthMethod.value = AI_PROVIDER_PRESETS["claude"].authMethod;
|
||||
aiEditEndpoint.value = AI_PROVIDER_PRESETS["claude"].endpoint;
|
||||
@@ -3823,6 +3834,7 @@ async function applyPendingAiConfigDeepLinkDraft() {
|
||||
aiEnterEditMode();
|
||||
aiEditConfigName.value = draft.name;
|
||||
aiEditProvider.value = draft.provider;
|
||||
aiEditProviderPresetId.value = getAiProviderPresetId(draft.provider, draft.endpoint);
|
||||
aiEditApiKey.value = "";
|
||||
aiEditAuthMethod.value = draft.authMethod;
|
||||
aiEditEndpoint.value = draft.endpoint;
|
||||
@@ -7057,7 +7069,7 @@ onUnmounted(() => {
|
||||
<div v-else class="space-y-2">
|
||||
<div v-for="config in displayedAiConfigs" :key="config.id" class="flex items-center justify-between rounded-md border p-3" :class="{ 'border-primary bg-primary/5': config.isDefault }">
|
||||
<div class="flex items-center gap-3">
|
||||
<AiProviderLogo :provider="config.provider" :label="config.provider" :icon-slug="AI_PROVIDER_PRESETS[config.provider].iconSlug" />
|
||||
<AiProviderLogo :provider="config.provider" :label="getAiProviderPreset(config.provider, config.endpoint).label" :icon-slug="getAiProviderPreset(config.provider, config.endpoint).iconSlug" :icon-path="getAiProviderPreset(config.provider, config.endpoint).iconPath" />
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium">{{ config.name }}</span>
|
||||
@@ -7066,7 +7078,7 @@ onUnmounted(() => {
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
{{ aiProviderLabel(config.provider, t) }}
|
||||
{{ getAiProviderPreset(config.provider, config.endpoint).label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -7309,28 +7321,51 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- Provider Selection -->
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<div class="grid grid-cols-3 items-start gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.provider") }}</Label>
|
||||
<Select :model-value="aiEditProvider" @update:model-value="(v: any) => aiSelectProvider(v)">
|
||||
<SelectTrigger class="col-span-2" inputClass="h-8 text-xs">
|
||||
<SelectValue>
|
||||
<span class="flex items-center gap-2">
|
||||
<AiProviderLogo :provider="selectedAiProviderPreset.provider" :label="aiProviderLabel(selectedAiProviderPreset.provider, t)" :icon-slug="selectedAiProviderPreset.iconSlug" />
|
||||
<span>{{ aiProviderLabel(selectedAiProviderPreset.provider, t) }}</span>
|
||||
</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="provider in aiProviderOptions" :key="provider.provider" :value="provider.provider">
|
||||
<span class="flex w-full items-center justify-between gap-4">
|
||||
<div class="col-span-2 space-y-2">
|
||||
<Select :model-value="aiEditProviderPresetId" @update:model-value="(v: any) => aiSelectProvider(String(v))">
|
||||
<SelectTrigger inputClass="h-8 text-xs">
|
||||
<SelectValue>
|
||||
<span class="flex items-center gap-2">
|
||||
<AiProviderLogo :provider="provider.provider" :label="aiProviderLabel(provider.provider, t)" :icon-slug="provider.iconSlug" />
|
||||
<span>{{ aiProviderLabel(provider.provider, t) }}</span>
|
||||
<AiProviderLogo :provider="selectedAiProviderPreset.provider" :label="selectedAiProviderPreset.label" :icon-slug="selectedAiProviderPreset.iconSlug" :icon-path="selectedAiProviderPreset.iconPath" />
|
||||
<span>{{ selectedAiProviderPreset.label }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent class="w-[32rem] max-w-[calc(100vw-2rem)]">
|
||||
<div class="grid grid-cols-1 gap-1 sm:grid-cols-2">
|
||||
<SelectGroup class="min-w-0">
|
||||
<SelectLabel>{{ t("ai.builtinProviders") }}</SelectLabel>
|
||||
<SelectItem v-for="provider in builtinAiProviderOptions" :key="provider.provider" :value="provider.provider">
|
||||
<span class="flex w-full min-w-0 items-center gap-2">
|
||||
<AiProviderLogo :provider="provider.provider" :label="provider.label" :icon-slug="provider.iconSlug" :icon-path="provider.iconPath" />
|
||||
<span class="truncate">{{ provider.label }}</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<SelectGroup v-if="partnerAiProviderOptions.length" class="min-w-0 border-border/60 sm:border-l">
|
||||
<SelectLabel>{{ t("ai.partnerProviders") }}</SelectLabel>
|
||||
<SelectItem v-for="provider in partnerAiProviderOptions" :key="provider.id" :value="provider.id">
|
||||
<span class="flex w-full min-w-0 items-center gap-2">
|
||||
<AiProviderLogo :provider="provider.provider" :label="provider.label" :icon-slug="provider.iconSlug" :icon-path="provider.iconPath" />
|
||||
<span class="min-w-0 flex-1 truncate">{{ provider.label }}</span>
|
||||
<Badge variant="outline" class="h-5 shrink-0 px-1.5 text-[10px] font-normal">{{ t("ai.jalapenoSponsored") }}</Badge>
|
||||
</span>
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</div>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div v-if="selectedAiPartnerPreset" class="flex items-center gap-3 rounded-md border border-primary/20 bg-primary/5 px-3 py-2">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-[11px] leading-4 text-muted-foreground">{{ t(selectedAiPartnerPreset.descriptionKey) }}</p>
|
||||
</div>
|
||||
<Button type="button" variant="ghost" size="icon" class="h-7 w-7 shrink-0" :title="t('ai.visitPartner')" :aria-label="t('ai.visitPartner')" @click="openExternalUrl(selectedAiPartnerPreset.websiteUrl)">
|
||||
<ExternalLink class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CLI MCP Status -->
|
||||
@@ -7388,7 +7423,13 @@ onUnmounted(() => {
|
||||
<!-- API Key -->
|
||||
<div v-if="!aiIsCliProvider" class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ aiCredentialLabel }}</Label>
|
||||
<PasswordInput v-model="aiEditApiKey" autocomplete="off" class="col-span-2" inputClass="h-8 text-xs" :placeholder="aiCredentialPlaceholder" />
|
||||
<div class="col-span-2 flex min-w-0 items-center gap-2">
|
||||
<PasswordInput v-model="aiEditApiKey" autocomplete="off" class="min-w-0 flex-1" inputClass="h-8 text-xs" :placeholder="aiCredentialPlaceholder" />
|
||||
<Button v-if="selectedAiPartnerPreset" type="button" variant="outline" size="sm" class="h-8 shrink-0 gap-1.5 px-3 text-xs" :title="t('ai.getApiKey')" :aria-label="t('ai.getApiKey')" @click="openExternalUrl(selectedAiPartnerPreset.apiKeyUrl)">
|
||||
{{ t("ai.getApiKey") }}
|
||||
<ExternalLink class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Endpoint -->
|
||||
|
||||
Reference in new issue
Block a user