feat(grok): 账单绝对金额、调度阈值 UI、搜索/Voice 计费与 /v1/web_search

- BillingSummary 输出 prepaid/monthly_used/on_demand 绝对金额,并映射官方 7d/30d 进度条
- 账号编辑页支持 credentials.account_scheduling_threshold 覆盖;Settings 文案细化
- groups.search_price_per_1k + 管理端/缓存全链路;SearchCount/AudioUsage 请求级计费
- Voice TTS/STT/Realtime 成功路径 RecordUsage;独立 /v1/web_search 原生 Grok 搜索
This commit is contained in:
IanShaw027
2026-08-07 15:52:55 +08:00
parent 99ad01f6de
commit 79df1647d4
46 changed files with 1665 additions and 77 deletions
+39 -4
View File
@@ -1490,12 +1490,24 @@
class="border-t border-gray-200 dark:border-dark-400 pt-4 mt-4"
>
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{{ t("admin.groups.voicePricing.title") }}
{{ t("admin.groups.explicitPricing.title") }}
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
{{ t("admin.groups.voicePricing.description") }}
{{ t("admin.groups.explicitPricing.description") }}
</p>
<div class="grid grid-cols-1 gap-3 md:grid-cols-3">
<div>
<label class="input-label">{{ t("admin.groups.explicitPricing.searchPricePer1k") }}</label>
<input
v-model.number="createForm.search_price_per_1k"
type="number"
step="0.000001"
min="0"
class="input"
:placeholder="t('admin.groups.explicitPricing.pricePlaceholder')"
data-testid="create-search-price"
/>
</div>
<div>
<label class="input-label">{{ t("admin.groups.voicePricing.audioRealtimePerMin") }}</label>
<input
@@ -3181,12 +3193,24 @@
class="border-t border-gray-200 dark:border-dark-400 pt-4 mt-4"
>
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{{ t("admin.groups.voicePricing.title") }}
{{ t("admin.groups.explicitPricing.title") }}
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
{{ t("admin.groups.voicePricing.description") }}
{{ t("admin.groups.explicitPricing.description") }}
</p>
<div class="grid grid-cols-1 gap-3 md:grid-cols-3">
<div>
<label class="input-label">{{ t("admin.groups.explicitPricing.searchPricePer1k") }}</label>
<input
v-model.number="editForm.search_price_per_1k"
type="number"
step="0.000001"
min="0"
class="input"
:placeholder="t('admin.groups.explicitPricing.pricePlaceholder')"
data-testid="edit-search-price"
/>
</div>
<div>
<label class="input-label">{{ t("admin.groups.voicePricing.audioRealtimePerMin") }}</label>
<input
@@ -4908,6 +4932,7 @@ const createForm = reactive({
video_model_prices: createVideoModelPricesForm(),
// Codex 网页搜索按次计费(仅 openai 平台使用);null = 使用默认价 0.01
web_search_price_per_call: null as number | null,
search_price_per_1k: null as number | null,
audio_realtime_price_per_min: null as number | null,
audio_tts_price_per_million_chars: null as number | null,
audio_stt_price_per_hour: null as number | null,
@@ -5266,6 +5291,7 @@ const editForm = reactive({
video_model_prices: createVideoModelPricesForm(),
// Codex 网页搜索按次计费(仅 openai 平台使用);null = 使用默认价 0.01
web_search_price_per_call: null as number | null,
search_price_per_1k: null as number | null,
audio_realtime_price_per_min: null as number | null,
audio_tts_price_per_million_chars: null as number | null,
audio_stt_price_per_hour: null as number | null,
@@ -5719,6 +5745,7 @@ const closeCreateModal = () => {
createForm.video_price_1080p = null;
createForm.video_model_prices = createVideoModelPricesForm();
createForm.web_search_price_per_call = null;
createForm.search_price_per_1k = null;
createForm.audio_realtime_price_per_min = null;
createForm.audio_tts_price_per_million_chars = null;
createForm.audio_stt_price_per_hour = null;
@@ -5886,6 +5913,9 @@ const handleCreateGroup = async () => {
requestData.video_price_480p = emptyToNull(requestData.video_price_480p);
requestData.video_price_720p = emptyToNull(requestData.video_price_720p);
requestData.video_price_1080p = emptyToNull(requestData.video_price_1080p);
requestData.search_price_per_1k = emptyToNull(
requestData.search_price_per_1k,
);
requestData.audio_realtime_price_per_min = emptyToNull(
requestData.audio_realtime_price_per_min,
);
@@ -5955,6 +5985,7 @@ const handleEdit = async (group: AdminGroup) => {
group.video_model_prices,
);
editForm.web_search_price_per_call = group.web_search_price_per_call ?? null;
editForm.search_price_per_1k = group.search_price_per_1k ?? null;
editForm.audio_realtime_price_per_min = group.audio_realtime_price_per_min ?? null;
editForm.audio_tts_price_per_million_chars = group.audio_tts_price_per_million_chars ?? null;
editForm.audio_stt_price_per_hour = group.audio_stt_price_per_hour ?? null;
@@ -6039,6 +6070,7 @@ const closeEditModal = () => {
editForm.video_price_1080p = null;
editForm.video_model_prices = createVideoModelPricesForm();
editForm.web_search_price_per_call = null;
editForm.search_price_per_1k = null;
editForm.audio_realtime_price_per_min = null;
editForm.audio_tts_price_per_million_chars = null;
editForm.audio_stt_price_per_hour = null;
@@ -6147,6 +6179,9 @@ const handleUpdateGroup = async () => {
payload.video_price_480p = emptyPriceToClear(payload.video_price_480p);
payload.video_price_720p = emptyPriceToClear(payload.video_price_720p);
payload.video_price_1080p = emptyPriceToClear(payload.video_price_1080p);
payload.search_price_per_1k = emptyPriceToClear(
payload.search_price_per_1k,
);
payload.audio_realtime_price_per_min = emptyPriceToClear(
payload.audio_realtime_price_per_min,
);