feat(frontend): show Grok monitoring and Free plan badge

This commit is contained in:
superman2003
2026-07-14 12:24:42 +08:00
parent 0a64a6d8ce
commit d2d3fcf57b
17 changed files with 423 additions and 18 deletions
@@ -109,6 +109,8 @@ import { useI18n } from 'vue-i18n'
import type { APIMode, BodyOverrideMode, Provider } from '@/api/admin/channelMonitor'
import {
API_MODE_RESPONSES,
DEFAULT_GROK_MODEL,
PROVIDER_GROK,
PROVIDER_OPENAI,
} from '@/constants/channelMonitor'
@@ -305,11 +307,12 @@ const bodyPlaceholder = computed(() => {
}
return '{\n "model": "gpt-4o-mini",\n "instructions": "You are a health check endpoint. Reply briefly.",\n "input": "Reply with exactly: ok",\n "max_output_tokens": 20,\n "stream": false\n}'
}
if (props.provider === PROVIDER_OPENAI) {
if (props.provider === PROVIDER_OPENAI || props.provider === PROVIDER_GROK) {
if (props.bodyOverrideMode === 'merge') {
return '{\n "max_tokens": 20\n}'
}
return '{\n "model": "gpt-4o-mini",\n "messages": [{"role":"user","content":"Reply with exactly: ok"}],\n "max_tokens": 20,\n "stream": false\n}'
const model = props.provider === PROVIDER_GROK ? DEFAULT_GROK_MODEL : 'gpt-4o-mini'
return `{\n "model": "${model}",\n "messages": [{"role":"user","content":"Reply with exactly: ok"}],\n "max_tokens": 20,\n "stream": false\n}`
}
if (props.bodyOverrideMode === 'merge') {
return '{\n "system": "You are Claude Code..."\n}'
@@ -70,6 +70,7 @@ import {
PROVIDER_OPENAI,
PROVIDER_ANTHROPIC,
PROVIDER_GEMINI,
PROVIDER_GROK,
} from '@/constants/channelMonitor'
defineProps<{
@@ -94,6 +95,7 @@ const providerFilterOptions = computed(() => [
{ value: PROVIDER_OPENAI, label: t('monitorCommon.providers.openai') },
{ value: PROVIDER_ANTHROPIC, label: t('monitorCommon.providers.anthropic') },
{ value: PROVIDER_GEMINI, label: t('monitorCommon.providers.gemini') },
{ value: PROVIDER_GROK, label: t('monitorCommon.providers.grok') },
])
const enabledFilterOptions = computed(() => [
@@ -13,15 +13,16 @@
<div>
<label class="input-label">{{ t('admin.channelMonitor.form.provider') }} <span class="text-red-500">*</span></label>
<div class="grid grid-cols-3 gap-3">
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
<button
v-for="opt in providerOptions"
:key="opt.value"
type="button"
:data-testid="`monitor-provider-${opt.value}`"
:aria-pressed="form.provider === opt.value"
class="flex items-center justify-center gap-2 rounded-lg border-2 px-3 py-2.5 text-sm font-medium transition-colors"
:class="providerPickerClass(opt.value, form.provider === opt.value)"
@click="form.provider = opt.value"
@click="selectProvider(opt.value)"
>
<ProviderIcon :provider="opt.value" :size="18" />
<span>{{ opt.label }}</span>
@@ -50,7 +51,7 @@
<div>
<label class="input-label">{{ t('admin.channelMonitor.form.endpoint') }} <span class="text-red-500">*</span></label>
<div class="flex gap-2">
<input v-model="form.endpoint" type="text" required class="input flex-1" :placeholder="t('admin.channelMonitor.form.endpointPlaceholder')" />
<input v-model="form.endpoint" data-testid="monitor-endpoint" type="text" required class="input flex-1" :placeholder="t('admin.channelMonitor.form.endpointPlaceholder')" />
<button type="button" @click="useCurrentDomain" class="btn btn-secondary whitespace-nowrap">
{{ t('admin.channelMonitor.form.useCurrentDomain') }}
</button>
@@ -80,6 +81,7 @@
<label class="input-label">{{ t('admin.channelMonitor.form.primaryModel') }} <span class="text-red-500">*</span></label>
<input
v-model="form.primary_model"
data-testid="monitor-primary-model"
type="text"
required
class="input font-medium"
@@ -213,8 +215,11 @@ import {
PROVIDER_OPENAI,
PROVIDER_ANTHROPIC,
PROVIDER_GEMINI,
PROVIDER_GROK,
API_MODE_CHAT_COMPLETIONS,
API_MODE_RESPONSES,
DEFAULT_GROK_ENDPOINT,
DEFAULT_GROK_MODEL,
DEFAULT_INTERVAL_SECONDS,
} from '@/constants/channelMonitor'
@@ -396,8 +401,26 @@ const providerOptions = computed<ProviderOption[]>(() => [
{ value: PROVIDER_ANTHROPIC, label: t('monitorCommon.providers.anthropic') },
{ value: PROVIDER_OPENAI, label: t('monitorCommon.providers.openai') },
{ value: PROVIDER_GEMINI, label: t('monitorCommon.providers.gemini') },
{ value: PROVIDER_GROK, label: t('monitorCommon.providers.grok') },
])
function selectProvider(provider: Provider) {
if (form.provider === provider) return
const previousProvider = form.provider
const clearGrokEndpoint =
previousProvider === PROVIDER_GROK && form.endpoint === DEFAULT_GROK_ENDPOINT
const clearGrokModel =
previousProvider === PROVIDER_GROK && form.primary_model === DEFAULT_GROK_MODEL
form.provider = provider
if (provider === PROVIDER_GROK) {
if (!form.endpoint.trim()) form.endpoint = DEFAULT_GROK_ENDPOINT
if (!form.primary_model.trim()) form.primary_model = DEFAULT_GROK_MODEL
return
}
if (clearGrokEndpoint) form.endpoint = ''
if (clearGrokModel) form.primary_model = ''
}
// Clear api_key whenever provider changes to avoid cross-provider key mismatch.
// Editing mode loads api_key='' via loadFromMonitor and only sets it on user
// typing, so clearing on provider change is always a safe no-op until the user
@@ -7,7 +7,7 @@
>
<!-- provider tabs -->
<div class="mb-4 border-b border-gray-200 dark:border-dark-700">
<div role="tablist" class="flex gap-1">
<div role="tablist" class="flex flex-wrap gap-1">
<button
v-for="tab in providerTabs"
:key="tab.value"
@@ -130,7 +130,7 @@
{{ t('admin.channelMonitor.form.provider') }}
<span class="text-red-500">*</span>
</label>
<div class="grid grid-cols-3 gap-3">
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
<button
v-for="opt in providerTabs"
:key="opt.value"
@@ -248,6 +248,7 @@ import {
PROVIDER_ANTHROPIC,
PROVIDER_OPENAI,
PROVIDER_GEMINI,
PROVIDER_GROK,
API_MODE_CHAT_COMPLETIONS,
API_MODE_RESPONSES,
} from '@/constants/channelMonitor'
@@ -267,6 +268,7 @@ const providerTabs = computed<{ value: Provider; label: string }[]>(() => [
{ value: PROVIDER_ANTHROPIC, label: t('monitorCommon.providers.anthropic') },
{ value: PROVIDER_OPENAI, label: t('monitorCommon.providers.openai') },
{ value: PROVIDER_GEMINI, label: t('monitorCommon.providers.gemini') },
{ value: PROVIDER_GROK, label: t('monitorCommon.providers.grok') },
])
const activeProvider = ref<Provider>(PROVIDER_ANTHROPIC)
@@ -282,6 +284,7 @@ const countByProvider = computed<Record<Provider, number>>(() => {
anthropic: 0,
openai: 0,
gemini: 0,
grok: 0,
}
for (const t of templates.value) out[t.provider]++
return out