feat(account): 账号测试支持选择模型
- 新增 GET /api/v1/admin/accounts/:id/models 接口获取账号可用模型 - 账号测试弹窗新增模型选择下拉框 - 测试时支持传入 model_id 参数,不传则默认使用 Sonnet - API Key 账号支持根据 model_mapping 映射测试模型 - 将模型常量提取到 claude 包统一管理
This commit is contained in:
@@ -3,6 +3,7 @@ package admin
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"sub2api/internal/pkg/claude"
|
||||||
"sub2api/internal/pkg/response"
|
"sub2api/internal/pkg/response"
|
||||||
"sub2api/internal/service"
|
"sub2api/internal/service"
|
||||||
|
|
||||||
@@ -186,6 +187,11 @@ func (h *AccountHandler) Delete(c *gin.Context) {
|
|||||||
response.Success(c, gin.H{"message": "Account deleted successfully"})
|
response.Success(c, gin.H{"message": "Account deleted successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAccountRequest represents the request body for testing an account
|
||||||
|
type TestAccountRequest struct {
|
||||||
|
ModelID string `json:"model_id"`
|
||||||
|
}
|
||||||
|
|
||||||
// Test handles testing account connectivity with SSE streaming
|
// Test handles testing account connectivity with SSE streaming
|
||||||
// POST /api/v1/admin/accounts/:id/test
|
// POST /api/v1/admin/accounts/:id/test
|
||||||
func (h *AccountHandler) Test(c *gin.Context) {
|
func (h *AccountHandler) Test(c *gin.Context) {
|
||||||
@@ -195,8 +201,12 @@ func (h *AccountHandler) Test(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var req TestAccountRequest
|
||||||
|
// Allow empty body, model_id is optional
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
|
||||||
// Use AccountTestService to test the account with SSE streaming
|
// Use AccountTestService to test the account with SSE streaming
|
||||||
if err := h.accountTestService.TestAccountConnection(c, accountID); err != nil {
|
if err := h.accountTestService.TestAccountConnection(c, accountID, req.ModelID); err != nil {
|
||||||
// Error already sent via SSE, just log
|
// Error already sent via SSE, just log
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -535,3 +545,58 @@ func (h *AccountHandler) SetSchedulable(c *gin.Context) {
|
|||||||
|
|
||||||
response.Success(c, account)
|
response.Success(c, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAvailableModels handles getting available models for an account
|
||||||
|
// GET /api/v1/admin/accounts/:id/models
|
||||||
|
func (h *AccountHandler) GetAvailableModels(c *gin.Context) {
|
||||||
|
accountID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, "Invalid account ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := h.adminService.GetAccount(c.Request.Context(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
response.NotFound(c, "Account not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For OAuth and Setup-Token accounts: return default models
|
||||||
|
if account.IsOAuth() {
|
||||||
|
response.Success(c, claude.DefaultModels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For API Key accounts: return models based on model_mapping
|
||||||
|
mapping := account.GetModelMapping()
|
||||||
|
if mapping == nil || len(mapping) == 0 {
|
||||||
|
// No mapping configured, return default models
|
||||||
|
response.Success(c, claude.DefaultModels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return mapped models (keys of the mapping are the available model IDs)
|
||||||
|
var models []claude.Model
|
||||||
|
for requestedModel := range mapping {
|
||||||
|
// Try to find display info from default models
|
||||||
|
var found bool
|
||||||
|
for _, dm := range claude.DefaultModels {
|
||||||
|
if dm.ID == requestedModel {
|
||||||
|
models = append(models, dm)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If not found in defaults, create a basic entry
|
||||||
|
if !found {
|
||||||
|
models = append(models, claude.Model{
|
||||||
|
ID: requestedModel,
|
||||||
|
Type: "model",
|
||||||
|
DisplayName: requestedModel,
|
||||||
|
CreatedAt: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(c, models)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"sub2api/internal/middleware"
|
"sub2api/internal/middleware"
|
||||||
"sub2api/internal/model"
|
"sub2api/internal/model"
|
||||||
|
"sub2api/internal/pkg/claude"
|
||||||
"sub2api/internal/service"
|
"sub2api/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -285,29 +286,8 @@ func (h *GatewayHandler) waitForSlotWithPing(c *gin.Context, slotType string, id
|
|||||||
// Models handles listing available models
|
// Models handles listing available models
|
||||||
// GET /v1/models
|
// GET /v1/models
|
||||||
func (h *GatewayHandler) Models(c *gin.Context) {
|
func (h *GatewayHandler) Models(c *gin.Context) {
|
||||||
models := []gin.H{
|
|
||||||
{
|
|
||||||
"id": "claude-opus-4-5-20251101",
|
|
||||||
"type": "model",
|
|
||||||
"display_name": "Claude Opus 4.5",
|
|
||||||
"created_at": "2025-11-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "claude-sonnet-4-5-20250929",
|
|
||||||
"type": "model",
|
|
||||||
"display_name": "Claude Sonnet 4.5",
|
|
||||||
"created_at": "2025-09-29T00:00:00Z",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "claude-haiku-4-5-20251001",
|
|
||||||
"type": "model",
|
|
||||||
"display_name": "Claude Haiku 4.5",
|
|
||||||
"created_at": "2025-10-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"data": models,
|
"data": claude.DefaultModels,
|
||||||
"object": "list",
|
"object": "list",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ package claude
|
|||||||
|
|
||||||
// Beta header 常量
|
// Beta header 常量
|
||||||
const (
|
const (
|
||||||
BetaOAuth = "oauth-2025-04-20"
|
BetaOAuth = "oauth-2025-04-20"
|
||||||
BetaClaudeCode = "claude-code-20250219"
|
BetaClaudeCode = "claude-code-20250219"
|
||||||
BetaInterleavedThinking = "interleaved-thinking-2025-05-14"
|
BetaInterleavedThinking = "interleaved-thinking-2025-05-14"
|
||||||
BetaFineGrainedToolStreaming = "fine-grained-tool-streaming-2025-05-14"
|
BetaFineGrainedToolStreaming = "fine-grained-tool-streaming-2025-05-14"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,3 +30,45 @@ var DefaultHeaders = map[string]string{
|
|||||||
"X-App": "cli",
|
"X-App": "cli",
|
||||||
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Model 表示一个 Claude 模型
|
||||||
|
type Model struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultModels Claude Code 客户端支持的默认模型列表
|
||||||
|
var DefaultModels = []Model{
|
||||||
|
{
|
||||||
|
ID: "claude-opus-4-5-20251101",
|
||||||
|
Type: "model",
|
||||||
|
DisplayName: "Claude Opus 4.5",
|
||||||
|
CreatedAt: "2025-11-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "claude-sonnet-4-5-20250929",
|
||||||
|
Type: "model",
|
||||||
|
DisplayName: "Claude Sonnet 4.5",
|
||||||
|
CreatedAt: "2025-09-29T00:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "claude-haiku-4-5-20251001",
|
||||||
|
Type: "model",
|
||||||
|
DisplayName: "Claude Haiku 4.5",
|
||||||
|
CreatedAt: "2025-10-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultModelIDs 返回默认模型的 ID 列表
|
||||||
|
func DefaultModelIDs() []string {
|
||||||
|
ids := make([]string, len(DefaultModels))
|
||||||
|
for i, m := range DefaultModels {
|
||||||
|
ids[i] = m.ID
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTestModel 测试时使用的默认模型
|
||||||
|
const DefaultTestModel = "claude-sonnet-4-5-20250929"
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ func registerRoutes(r *gin.Engine, h *handler.Handlers, s *service.Services, rep
|
|||||||
accounts.GET("/:id/today-stats", h.Admin.Account.GetTodayStats)
|
accounts.GET("/:id/today-stats", h.Admin.Account.GetTodayStats)
|
||||||
accounts.POST("/:id/clear-rate-limit", h.Admin.Account.ClearRateLimit)
|
accounts.POST("/:id/clear-rate-limit", h.Admin.Account.ClearRateLimit)
|
||||||
accounts.POST("/:id/schedulable", h.Admin.Account.SetSchedulable)
|
accounts.POST("/:id/schedulable", h.Admin.Account.SetSchedulable)
|
||||||
|
accounts.GET("/:id/models", h.Admin.Account.GetAvailableModels)
|
||||||
accounts.POST("/batch", h.Admin.Account.BatchCreate)
|
accounts.POST("/batch", h.Admin.Account.BatchCreate)
|
||||||
|
|
||||||
// OAuth routes
|
// OAuth routes
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
testClaudeAPIURL = "https://api.anthropic.com/v1/messages"
|
testClaudeAPIURL = "https://api.anthropic.com/v1/messages"
|
||||||
testModel = "claude-sonnet-4-5-20250929"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestEvent represents a SSE event for account testing
|
// TestEvent represents a SSE event for account testing
|
||||||
@@ -64,9 +63,9 @@ func generateSessionString() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createTestPayload creates a Claude Code style test request payload
|
// createTestPayload creates a Claude Code style test request payload
|
||||||
func createTestPayload() map[string]interface{} {
|
func createTestPayload(modelID string) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"model": testModel,
|
"model": modelID,
|
||||||
"messages": []map[string]interface{}{
|
"messages": []map[string]interface{}{
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
@@ -101,7 +100,8 @@ func createTestPayload() map[string]interface{} {
|
|||||||
|
|
||||||
// TestAccountConnection tests an account's connection by sending a test request
|
// TestAccountConnection tests an account's connection by sending a test request
|
||||||
// All account types use full Claude Code client characteristics, only auth header differs
|
// All account types use full Claude Code client characteristics, only auth header differs
|
||||||
func (s *AccountTestService) TestAccountConnection(c *gin.Context, accountID int64) error {
|
// modelID is optional - if empty, defaults to claude.DefaultTestModel
|
||||||
|
func (s *AccountTestService) TestAccountConnection(c *gin.Context, accountID int64, modelID string) error {
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
// Get account
|
// Get account
|
||||||
@@ -110,6 +110,22 @@ func (s *AccountTestService) TestAccountConnection(c *gin.Context, accountID int
|
|||||||
return s.sendErrorAndEnd(c, "Account not found")
|
return s.sendErrorAndEnd(c, "Account not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the model to use
|
||||||
|
testModelID := modelID
|
||||||
|
if testModelID == "" {
|
||||||
|
testModelID = claude.DefaultTestModel
|
||||||
|
}
|
||||||
|
|
||||||
|
// For API Key accounts with model mapping, map the model
|
||||||
|
if account.Type == "apikey" {
|
||||||
|
mapping := account.GetModelMapping()
|
||||||
|
if mapping != nil && len(mapping) > 0 {
|
||||||
|
if mappedModel, exists := mapping[testModelID]; exists {
|
||||||
|
testModelID = mappedModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determine authentication method and API URL
|
// Determine authentication method and API URL
|
||||||
var authToken string
|
var authToken string
|
||||||
var useBearer bool
|
var useBearer bool
|
||||||
@@ -165,11 +181,11 @@ func (s *AccountTestService) TestAccountConnection(c *gin.Context, accountID int
|
|||||||
c.Writer.Flush()
|
c.Writer.Flush()
|
||||||
|
|
||||||
// Create Claude Code style payload (same for all account types)
|
// Create Claude Code style payload (same for all account types)
|
||||||
payload := createTestPayload()
|
payload := createTestPayload(testModelID)
|
||||||
payloadBytes, _ := json.Marshal(payload)
|
payloadBytes, _ := json.Marshal(payload)
|
||||||
|
|
||||||
// Send test_start event
|
// Send test_start event
|
||||||
s.sendEvent(c, TestEvent{Type: "test_start", Model: testModel})
|
s.sendEvent(c, TestEvent{Type: "test_start", Model: testModelID})
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(payloadBytes))
|
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(payloadBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
PaginatedResponse,
|
PaginatedResponse,
|
||||||
AccountUsageInfo,
|
AccountUsageInfo,
|
||||||
WindowStats,
|
WindowStats,
|
||||||
|
ClaudeModel,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,6 +248,16 @@ export async function setSchedulable(id: number, schedulable: boolean): Promise<
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available models for an account
|
||||||
|
* @param id - Account ID
|
||||||
|
* @returns List of available models for this account
|
||||||
|
*/
|
||||||
|
export async function getAvailableModels(id: number): Promise<ClaudeModel[]> {
|
||||||
|
const { data } = await apiClient.get<ClaudeModel[]>(`/admin/accounts/${id}/models`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
export const accountsAPI = {
|
export const accountsAPI = {
|
||||||
list,
|
list,
|
||||||
getById,
|
getById,
|
||||||
@@ -262,6 +273,7 @@ export const accountsAPI = {
|
|||||||
getTodayStats,
|
getTodayStats,
|
||||||
clearRateLimit,
|
clearRateLimit,
|
||||||
setSchedulable,
|
setSchedulable,
|
||||||
|
getAvailableModels,
|
||||||
generateAuthUrl,
|
generateAuthUrl,
|
||||||
exchangeCode,
|
exchangeCode,
|
||||||
batchCreate,
|
batchCreate,
|
||||||
|
|||||||
@@ -36,6 +36,23 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Model Selection -->
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
{{ t('admin.accounts.selectTestModel') }}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
v-model="selectedModelId"
|
||||||
|
:disabled="loadingModels || status === 'connecting'"
|
||||||
|
class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-dark-500 bg-white dark:bg-dark-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<option v-if="loadingModels" value="">{{ t('common.loading') }}...</option>
|
||||||
|
<option v-for="model in availableModels" :key="model.id" :value="model.id">
|
||||||
|
{{ model.display_name }} ({{ model.id }})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Terminal Output -->
|
<!-- Terminal Output -->
|
||||||
<div class="relative group">
|
<div class="relative group">
|
||||||
<div
|
<div
|
||||||
@@ -125,10 +142,10 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="startTest"
|
@click="startTest"
|
||||||
:disabled="status === 'connecting'"
|
:disabled="status === 'connecting' || !selectedModelId"
|
||||||
:class="[
|
:class="[
|
||||||
'px-4 py-2 text-sm font-medium rounded-lg transition-all flex items-center gap-2',
|
'px-4 py-2 text-sm font-medium rounded-lg transition-all flex items-center gap-2',
|
||||||
status === 'connecting'
|
status === 'connecting' || !selectedModelId
|
||||||
? 'bg-primary-400 text-white cursor-not-allowed'
|
? 'bg-primary-400 text-white cursor-not-allowed'
|
||||||
: status === 'success'
|
: status === 'success'
|
||||||
? 'bg-green-500 hover:bg-green-600 text-white'
|
? 'bg-green-500 hover:bg-green-600 text-white'
|
||||||
@@ -161,7 +178,8 @@
|
|||||||
import { ref, watch, nextTick } from 'vue'
|
import { ref, watch, nextTick } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Modal from '@/components/common/Modal.vue'
|
import Modal from '@/components/common/Modal.vue'
|
||||||
import type { Account } from '@/types'
|
import { adminAPI } from '@/api/admin'
|
||||||
|
import type { Account, ClaudeModel } from '@/types'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -184,17 +202,44 @@ const status = ref<'idle' | 'connecting' | 'success' | 'error'>('idle')
|
|||||||
const outputLines = ref<OutputLine[]>([])
|
const outputLines = ref<OutputLine[]>([])
|
||||||
const streamingContent = ref('')
|
const streamingContent = ref('')
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
const availableModels = ref<ClaudeModel[]>([])
|
||||||
|
const selectedModelId = ref('')
|
||||||
|
const loadingModels = ref(false)
|
||||||
let eventSource: EventSource | null = null
|
let eventSource: EventSource | null = null
|
||||||
|
|
||||||
// Reset state when modal opens
|
// Load available models when modal opens
|
||||||
watch(() => props.show, (newVal) => {
|
watch(() => props.show, async (newVal) => {
|
||||||
if (newVal) {
|
if (newVal && props.account) {
|
||||||
resetState()
|
resetState()
|
||||||
|
await loadAvailableModels()
|
||||||
} else {
|
} else {
|
||||||
closeEventSource()
|
closeEventSource()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const loadAvailableModels = async () => {
|
||||||
|
if (!props.account) return
|
||||||
|
|
||||||
|
loadingModels.value = true
|
||||||
|
selectedModelId.value = '' // Reset selection before loading
|
||||||
|
try {
|
||||||
|
availableModels.value = await adminAPI.accounts.getAvailableModels(props.account.id)
|
||||||
|
// Default to first model (usually Sonnet)
|
||||||
|
if (availableModels.value.length > 0) {
|
||||||
|
// Try to select Sonnet as default, otherwise use first model
|
||||||
|
const sonnetModel = availableModels.value.find(m => m.id.includes('sonnet'))
|
||||||
|
selectedModelId.value = sonnetModel?.id || availableModels.value[0].id
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load available models:', error)
|
||||||
|
// Fallback to empty list
|
||||||
|
availableModels.value = []
|
||||||
|
selectedModelId.value = ''
|
||||||
|
} finally {
|
||||||
|
loadingModels.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
status.value = 'idle'
|
status.value = 'idle'
|
||||||
outputLines.value = []
|
outputLines.value = []
|
||||||
@@ -227,7 +272,7 @@ const scrollToBottom = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const startTest = async () => {
|
const startTest = async () => {
|
||||||
if (!props.account) return
|
if (!props.account || !selectedModelId.value) return
|
||||||
|
|
||||||
resetState()
|
resetState()
|
||||||
status.value = 'connecting'
|
status.value = 'connecting'
|
||||||
@@ -247,7 +292,8 @@ const startTest = async () => {
|
|||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`,
|
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
},
|
||||||
|
body: JSON.stringify({ model_id: selectedModelId.value })
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -777,6 +777,7 @@ export default {
|
|||||||
copyOutput: 'Copy output',
|
copyOutput: 'Copy output',
|
||||||
startingTestForAccount: 'Starting test for account: {name}',
|
startingTestForAccount: 'Starting test for account: {name}',
|
||||||
testAccountTypeLabel: 'Account type: {type}',
|
testAccountTypeLabel: 'Account type: {type}',
|
||||||
|
selectTestModel: 'Select Test Model',
|
||||||
testModel: 'claude-sonnet-4-5-20250929',
|
testModel: 'claude-sonnet-4-5-20250929',
|
||||||
testPrompt: 'Prompt: "hi"',
|
testPrompt: 'Prompt: "hi"',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -865,6 +865,7 @@ export default {
|
|||||||
copyOutput: '复制输出',
|
copyOutput: '复制输出',
|
||||||
startingTestForAccount: '开始测试账号:{name}',
|
startingTestForAccount: '开始测试账号:{name}',
|
||||||
testAccountTypeLabel: '账号类型:{type}',
|
testAccountTypeLabel: '账号类型:{type}',
|
||||||
|
selectTestModel: '选择测试模型',
|
||||||
testModel: 'claude-sonnet-4-5-20250929',
|
testModel: 'claude-sonnet-4-5-20250929',
|
||||||
testPrompt: '提示词:"hi"',
|
testPrompt: '提示词:"hi"',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -285,6 +285,14 @@ export type AccountType = 'oauth' | 'setup-token' | 'apikey';
|
|||||||
export type OAuthAddMethod = 'oauth' | 'setup-token';
|
export type OAuthAddMethod = 'oauth' | 'setup-token';
|
||||||
export type ProxyProtocol = 'http' | 'https' | 'socks5';
|
export type ProxyProtocol = 'http' | 'https' | 'socks5';
|
||||||
|
|
||||||
|
// Claude Model type (returned by /v1/models and account models API)
|
||||||
|
export interface ClaudeModel {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
display_name: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Proxy {
|
export interface Proxy {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user