diff --git a/AGENTS.md b/AGENTS.md index b5750f7..76ac506 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -345,7 +345,6 @@ Documentation/usage lookup: - Use `context7` for latest dependency usage. - ## 🚨 Local Quality Checks (Optional Flow) After a set of changes, run these three checks in parallel instead of full lint immediately: diff --git a/README.pt-BR.md b/README.pt-BR.md index fb237bf..9e9b959 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -50,6 +50,7 @@ Quando você usa suas IDEs favoritas, já encontrou estes problemas? Este projeto contém melhorias significativas de desenvolvimento, correção de erros e melhorias de configuração em relação ao projeto original base. Principais melhorias incluem: + - **Interface de Gerenciamento de API Aprimorada:** Seletores dinâmicos de modelos e recursos de "Copiar Nome da API" implementados para facilitar a integração com outras ferramentas. - **Streaming Robusto (SSE):** Correção de bugs críticos no streaming via Server-Sent Events (SSE) no endpoint proxy `/v1/chat/completions` para suportar perfeitamente IDEs como Cursor, OpenCode e Windsurf. - **Rebranding e Localização:** Rebranding completo para Gemini Nexus com traduções melhoradas em Português (Brasil) e padrões em Inglês. diff --git a/package.json b/package.json index 35b7134..7ba5207 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "main": ".vite/build/main.js", "private": true, "scripts": { - "start": "electron-forge start", - "dev": "cross-env DEBUG=\"electron-forge:*\" electron-forge start", + "start": "cross-env DEBUG=electron-forge:* electron-forge start", + "dev": "cross-env DEBUG=electron-forge:* electron-forge start", "package": "electron-forge package", "make": "electron-forge make", "publish": "electron-forge publish", diff --git a/rename-deep.js b/rename-deep.js index 500c3d0..c7eb8b3 100644 --- a/rename-deep.js +++ b/rename-deep.js @@ -7,8 +7,28 @@ const projectRoot = process.cwd(); const allowedExtensions = /\.(ts|tsx|js|jsx|json|md|html|jsonc|yml|yaml|css)$/; // Arquivos/Pastas a ignorar (já curados ou irrelevantes) -const ignoreDirs = ['node_modules', '.git', '.gemini', 'dist', 'out', 'build', 'images', 'docs', '.planning']; -const ignoreFiles = ['README.md', 'README.pt-BR.md', 'CHANGELOG.md', 'forge.config.ts', 'package.json', 'rename.js', 'rename-deep.js', 'task.md', 'walkthrough.md']; +const ignoreDirs = [ + 'node_modules', + '.git', + '.gemini', + 'dist', + 'out', + 'build', + 'images', + 'docs', + '.planning', +]; +const ignoreFiles = [ + 'README.md', + 'README.pt-BR.md', + 'CHANGELOG.md', + 'forge.config.ts', + 'package.json', + 'rename.js', + 'rename-deep.js', + 'task.md', + 'walkthrough.md', +]; function walkAndReplace(dir) { const files = fs.readdirSync(dir); @@ -21,7 +41,7 @@ function walkAndReplace(dir) { if (stat.isDirectory()) { walkAndReplace(fullPath); - + // Rename directory if it contains 'antigravity' if (file.toLowerCase().includes('antigravity')) { const newName = file.replace(/antigravity/gi, (match) => { @@ -52,7 +72,7 @@ function walkAndReplace(dir) { // 2. String "Antigravity " -> "Gemini Nexus " content = content.replace(/Antigravity /g, 'Gemini Nexus '); - + // 3. String " Antigravity" -> " Gemini Nexus" content = content.replace(/ Antigravity/g, ' Gemini Nexus'); @@ -63,7 +83,10 @@ function walkAndReplace(dir) { content = content.replace(/antigravity/g, 'geminiNexus'); // Restore GitHub links - content = content.replace(new RegExp(tempUrlMarker, 'g'), 'github.com/Draculabo/AntigravityManager'); + content = content.replace( + new RegExp(tempUrlMarker, 'g'), + 'github.com/Draculabo/AntigravityManager', + ); // Cleanup any double replacements content = content.replace(/GeminiNexus Nexus/g, 'Gemini Nexus'); @@ -72,7 +95,7 @@ function walkAndReplace(dir) { fs.writeFileSync(fullPath, content, 'utf-8'); console.log(`Updated file content: ${fullPath}`); } - + // Rename file if it contains 'antigravity' if (file.toLowerCase().includes('antigravity')) { const newName = file.replace(/antigravity/gi, (match) => { diff --git a/src/components/AccountProxySettings.tsx b/src/components/AccountProxySettings.tsx index d1a7aec..d78547c 100644 --- a/src/components/AccountProxySettings.tsx +++ b/src/components/AccountProxySettings.tsx @@ -14,16 +14,16 @@ function AccountProxyItem({ account }: { account: CloudAccount }) { const [proxySaved, setProxySaved] = useState(false); return ( -
+
{account.avatar_url ? ( {account.name ) : ( -
+
{account.name?.[0]?.toUpperCase() || 'A'}
)} @@ -31,7 +31,7 @@ function AccountProxyItem({ account }: { account: CloudAccount }) { {account.name || t('cloud.card.unknown')} - {account.email} + {account.email}
@@ -67,7 +67,9 @@ function AccountProxyItem({ account }: { account: CloudAccount }) { />
{proxySaved && ( - {t('cloud.card.proxySaved')} + + {t('cloud.card.proxySaved')} + )}
@@ -87,7 +89,7 @@ export function AccountProxySettings() { {t('settings.proxy.accountProxies.description')} - + ); diff --git a/src/ipc/database/cloudHandler.ts b/src/ipc/database/cloudHandler.ts index 793b687..00228c6 100644 --- a/src/ipc/database/cloudHandler.ts +++ b/src/ipc/database/cloudHandler.ts @@ -1078,14 +1078,9 @@ export class CloudAccountRepo { 'enterpriseGcpProjectId', projectPayload, ); - this.upsertItemValue( - tx, - 'geminiNexusUnifiedStateSync.enterprisePreferences', - projectEntry, - ); + this.upsertItemValue(tx, 'geminiNexusUnifiedStateSync.enterprisePreferences', projectEntry); } else { - tx - .delete(itemTable) + tx.delete(itemTable) .where(eq(itemTable.key, 'geminiNexusUnifiedStateSync.enterprisePreferences')) .run(); } diff --git a/src/ipc/device/handler.ts b/src/ipc/device/handler.ts index 56c7b9a..0c2395e 100644 --- a/src/ipc/device/handler.ts +++ b/src/ipc/device/handler.ts @@ -182,11 +182,7 @@ function getTelemetryField(storage: Record, key: string): strin } function ensureTelemetryObject(storage: Record): Record { - if ( - !storage.telemetry || - !isObjectLike(storage.telemetry) || - Array.isArray(storage.telemetry) - ) { + if (!storage.telemetry || !isObjectLike(storage.telemetry) || Array.isArray(storage.telemetry)) { storage.telemetry = {}; } return storage.telemetry as Record; diff --git a/src/ipc/process/handler.ts b/src/ipc/process/handler.ts index 599fa92..e185b07 100644 --- a/src/ipc/process/handler.ts +++ b/src/ipc/process/handler.ts @@ -121,11 +121,7 @@ export async function isProcessRunning(): Promise { const cmd = proc.cmd?.toLowerCase() || ''; // Skip manager process - if ( - name.includes('manager') || - cmd.includes('manager') || - cmd.includes('gemini-nexus') - ) { + if (name.includes('manager') || cmd.includes('manager') || cmd.includes('gemini-nexus')) { continue; } diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx index 7fba3e4..5a7b02c 100644 --- a/src/layouts/MainLayout.tsx +++ b/src/layouts/MainLayout.tsx @@ -78,7 +78,11 @@ export const MainLayout: React.FC = () => {
- Gemini Nexus Logo + Gemini Nexus Logo
{ - if (Array.isArray(m.content)) { - return m.content.some((b) => b.type === 'tool_use'); - } - return false; - }); - - if (hasFunctionCalls && !hasValidSignatureForFunctionCalls(claudeReq.messages, globalSig)) { - if (!isGeminiFlashModel(config.finalModel)) { - isThinkingEnabled = false; - } - } - } + // Thinking is always kept enabled for thinking-capable models. + // All functionCall/functionResponse/thinking parts get unconditional + // thoughtSignature injection in buildContents(), so no guard is needed here. const generationConfig = buildGenerationConfig( claudeReq, @@ -137,7 +125,7 @@ export function transformClaudeRequestIn( systemInstruction?: { parts: { text: string }[] }; generationConfig?: GenerationConfig; tools?: GeminiToolDeclaration[]; - toolConfig?: { functionCallingConfig: { mode: string } }; + toolConfig?: { functionCallingConfig: { mode: string; allowedFunctionNames?: string[] } }; } = { contents, safetySettings, @@ -155,7 +143,27 @@ export function transformClaudeRequestIn( if (tools) { innerRequest.tools = tools; - innerRequest.toolConfig = { functionCallingConfig: { mode: 'VALIDATED' } }; + const toolChoice = claudeReq.tool_choice; + if (toolChoice === 'none') { + innerRequest.toolConfig = { functionCallingConfig: { mode: 'NONE' } }; + } else if (toolChoice === 'auto' || toolChoice === undefined) { + innerRequest.toolConfig = { functionCallingConfig: { mode: 'AUTO' } }; + } else if ( + typeof toolChoice === 'object' && + toolChoice.type === 'function' && + toolChoice.function?.name + ) { + innerRequest.toolConfig = { + functionCallingConfig: { + mode: 'ANY', + allowedFunctionNames: [toolChoice.function.name], + }, + }; + } else { + innerRequest.toolConfig = { functionCallingConfig: { mode: 'AUTO' } }; + } + } else if (claudeReq.tool_choice === 'none') { + innerRequest.toolConfig = { functionCallingConfig: { mode: 'NONE' } }; } // Inject googleSearch tool if needed (and not already done by buildTools) @@ -396,36 +404,8 @@ function injectGoogleSearchTool(body: { tools?: GeminiToolDeclaration[] }, mappe function buildSystemInstruction( system: ClaudeRequest['system'], ): { parts: { text: string }[] } | null { - const assistantIdentityDirective = - 'You are Gemini Nexus, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.\n' + - 'You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.\n' + - '**Absolute paths only**\n' + - '**Proactiveness**'; - const identityMarker = 'You are Gemini Nexus'; - const parts: { text: string }[] = []; - let hasIdentityDirective = false; - - if (system) { - if (isString(system)) { - if (system.includes(identityMarker)) { - hasIdentityDirective = true; - } - } else if (Array.isArray(system)) { - for (const block of system) { - if (block.type === 'text' && block.text.includes(identityMarker)) { - hasIdentityDirective = true; - break; - } - } - } - } - - if (!hasIdentityDirective) { - parts.push({ text: assistantIdentityDirective }); - } - if (system) { if (isString(system)) { parts.push({ text: system }); @@ -436,7 +416,6 @@ function buildSystemInstruction( } } - // If we pushed at least something if (parts.length > 0) { return { parts }; } @@ -501,7 +480,10 @@ function buildContents( for (let i = 0; i < messages.length; i++) { const msg = messages[i]; - const role = msg.role === 'assistant' ? 'model' : msg.role; + let role = msg.role === 'assistant' ? 'model' : msg.role; + if (role === 'function') { + role = 'user'; + } const parts: { text?: string; inlineData?: { @@ -518,14 +500,25 @@ function buildContents( for (const block of contentBlocks) { if (block.type === 'text') { - if (block.text && block.text !== '(no content)' && !isEmpty(block.text.trim())) - parts.push({ text: block.text.trim() }); + if (block.text && block.text !== '(no content)' && !isEmpty(block.text.trim())) { + let text = block.text.trim(); + if (msg.role === 'function' && msg.name) { + text = `Function result (${msg.name}):\n${text}`; + } else if (msg.name) { + text = `[${msg.name}]: ${text}`; + } + parts.push({ text }); + } } else if (block.type === 'thinking') { const part: any = { text: block.thinking, thought: true }; cleanJsonSchema(part); if (block.signature) { lastThoughtSignature = block.signature; part.thoughtSignature = block.signature; + } else { + // Inject fallback signature for thinking blocks without one + const fallbackSig = SignatureStore.get() || 'skip_thought_signature_validator'; + part.thoughtSignature = fallbackSig; } parts.push(part); } else if (block.type === 'image') { @@ -537,12 +530,10 @@ function buildContents( const part: any = { functionCall: { name: block.name, args: block.input, id: block.id } }; cleanJsonSchema(part); toolIdToName.set(block.id, block.name); + // Always inject thoughtSignature on functionCall parts. + // The v1internal API rejects functionCalls without signatures when thinking is enabled. const finalSig = block.signature || lastThoughtSignature || SignatureStore.get(); - if (finalSig) { - part.thoughtSignature = finalSig; - } else if (isThinkingEnabled && isGeminiFlashModel(mappedModel)) { - part.thoughtSignature = 'skip_thought_signature_validator'; - } + part.thoughtSignature = finalSig || 'skip_thought_signature_validator'; parts.push(part); } else if (block.type === 'tool_result') { const funcName = toolIdToName.get(block.tool_use_id) || block.tool_use_id; @@ -560,11 +551,13 @@ function buildContents( const part: any = { functionResponse: { name: funcName, - response: { result: mergedContent }, + response: block.is_error ? { error: mergedContent } : { result: mergedContent }, id: block.tool_use_id, }, }; - if (lastThoughtSignature) part.thoughtSignature = lastThoughtSignature; + // Always inject thoughtSignature on functionResponse parts for v1internal compatibility + part.thoughtSignature = + lastThoughtSignature || SignatureStore.get() || 'skip_thought_signature_validator'; parts.push(part); } else if (block.type === 'redacted_thinking') { parts.push({ text: `[Redacted Thinking: ${block.data}]`, thought: true }); @@ -669,11 +662,13 @@ function buildGenerationConfig( return thinkingConfig; }; + const maxTokens = claudeReq.max_completion_tokens ?? claudeReq.max_tokens; + if (isOpenAIPath) { config.temperature = claudeReq.temperature ?? 1.0; config.topP = claudeReq.top_p ?? 0.95; - if (claudeReq.max_tokens !== undefined) { - config.maxOutputTokens = claudeReq.max_tokens; + if (maxTokens !== undefined) { + config.maxOutputTokens = maxTokens; } else { config.maxOutputTokens = getMaxOutputTokens(mappedModel); } @@ -683,25 +678,39 @@ function buildGenerationConfig( if (isThinkingEnabled) { config.thinkingConfig = buildThinkingConfig(); } - return config; + } else { + if (isThinkingEnabled) { + config.thinkingConfig = buildThinkingConfig(); + } + if (claudeReq.temperature !== undefined) { + config.temperature = claudeReq.temperature; + } + if (claudeReq.top_p !== undefined) { + config.topP = claudeReq.top_p; + } + if (claudeReq.top_k !== undefined) { + config.topK = claudeReq.top_k; + } + if (maxTokens !== undefined) { + config.maxOutputTokens = maxTokens; + } + const defaultStopSequences = ['<|user|>', '<|endoftext|>', '<|end_of_turn|>', '[DONE]', '\n\nHuman:']; + if (claudeReq.stop_sequences && claudeReq.stop_sequences.length > 0) { + config.stopSequences = [...new Set([...claudeReq.stop_sequences, ...defaultStopSequences])]; + } else { + config.stopSequences = defaultStopSequences; + } } - if (isThinkingEnabled) { - config.thinkingConfig = buildThinkingConfig(); + if (claudeReq.response_format) { + if (claudeReq.response_format.type === 'json_object') { + config.responseMimeType = 'application/json'; + } else if (claudeReq.response_format.type === 'json_schema' && claudeReq.response_format.json_schema) { + config.responseMimeType = 'application/json'; + config.responseSchema = cleanJsonSchema(claudeReq.response_format.json_schema); + } } - if (claudeReq.temperature !== undefined) { - config.temperature = claudeReq.temperature; - } - if (claudeReq.top_p !== undefined) { - config.topP = claudeReq.top_p; - } - if (claudeReq.top_k !== undefined) { - config.topK = claudeReq.top_k; - } - if (claudeReq.max_tokens !== undefined) { - config.maxOutputTokens = claudeReq.max_tokens; - } - config.stopSequences = ['<|user|>', '<|endoftext|>', '<|end_of_turn|>', '[DONE]', '\n\nHuman:']; + return config; } diff --git a/src/lib/geminiNexus/ClaudeResponseMapper.ts b/src/lib/geminiNexus/ClaudeResponseMapper.ts index bd16192..51c71a2 100644 --- a/src/lib/geminiNexus/ClaudeResponseMapper.ts +++ b/src/lib/geminiNexus/ClaudeResponseMapper.ts @@ -23,7 +23,7 @@ class NonStreamingProcessor { constructor() {} - public process(geminiResponse: GeminiResponse): ClaudeResponse { + public process(geminiResponse: GeminiResponse, modelName?: string): ClaudeResponse { const candidate = geminiResponse.candidates?.[0]; const parts = candidate?.content?.parts || []; @@ -52,7 +52,7 @@ class NonStreamingProcessor { } // 5. Build response - return this.buildResponse(geminiResponse); + return this.buildResponse(geminiResponse, modelName); } private processPart(part: GeminiPart) { @@ -213,7 +213,7 @@ class NonStreamingProcessor { this.thinkingSignature = null; } - private buildResponse(geminiResponse: GeminiResponse): ClaudeResponse { + private buildResponse(geminiResponse: GeminiResponse, modelName?: string): ClaudeResponse { const finishReason = geminiResponse.candidates?.[0]?.finishReason; let stopReason = 'end_turn'; @@ -221,6 +221,15 @@ class NonStreamingProcessor { stopReason = 'tool_use'; } else if (finishReason === 'MAX_TOKENS') { stopReason = 'max_tokens'; + } else if ( + finishReason === 'SAFETY' || + finishReason === 'RECITATION' || + finishReason === 'OTHER' || + finishReason === 'IMAGE_SAFETY' || + finishReason === 'PROHIBITED_CONTENT' || + finishReason === 'SPII' + ) { + stopReason = 'stop_sequence'; } const usage: Usage = { @@ -234,7 +243,7 @@ class NonStreamingProcessor { id: geminiResponse.responseId || `msg_${uuidv4()}`, type: 'message', role: 'assistant', - model: geminiResponse.modelVersion || '', + model: geminiResponse.modelVersion || modelName || '', content: this.contentBlocks, stop_reason: stopReason, usage: usage, @@ -245,7 +254,7 @@ class NonStreamingProcessor { /** * Public API: Transform Gemini Response to Claude Response */ -export function transformResponse(geminiResponse: GeminiResponse): ClaudeResponse { +export function transformResponse(geminiResponse: GeminiResponse, modelName?: string): ClaudeResponse { const processor = new NonStreamingProcessor(); - return processor.process(geminiResponse); + return processor.process(geminiResponse, modelName); } diff --git a/src/lib/geminiNexus/ClaudeStreamingMapper.ts b/src/lib/geminiNexus/ClaudeStreamingMapper.ts index f506099..ee81622 100644 --- a/src/lib/geminiNexus/ClaudeStreamingMapper.ts +++ b/src/lib/geminiNexus/ClaudeStreamingMapper.ts @@ -53,7 +53,7 @@ export class StreamingState { return `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`; } - public emitMessageStart(rawJson: any): string { + public emitMessageStart(rawJson: any, modelName?: string): string { if (this.messageStartSent) return ''; const usageMeta = rawJson.usageMetadata; @@ -69,7 +69,7 @@ export class StreamingState { type: 'message', role: 'assistant', content: [], - model: rawJson.modelVersion || '', + model: rawJson.modelVersion || modelName || '', stop_reason: null, stop_sequence: null, usage: usage, diff --git a/src/lib/geminiNexus/ModelMapping.ts b/src/lib/geminiNexus/ModelMapping.ts index 979ccfa..440f69b 100644 --- a/src/lib/geminiNexus/ModelMapping.ts +++ b/src/lib/geminiNexus/ModelMapping.ts @@ -139,6 +139,10 @@ export function getAllDynamicModels( modelIds.add(modelId); } + for (const aliasModelId of Object.keys(CLAUDE_TO_GEMINI)) { + modelIds.add(aliasModelId); + } + if (dynamicModelIds) { for (const dynamicModelId of dynamicModelIds) { if (isString(dynamicModelId) && !isEmpty(dynamicModelId.trim())) { diff --git a/src/lib/geminiNexus/QuotaService.ts b/src/lib/geminiNexus/QuotaService.ts index 798cc41..a028f33 100644 --- a/src/lib/geminiNexus/QuotaService.ts +++ b/src/lib/geminiNexus/QuotaService.ts @@ -164,9 +164,7 @@ export class QuotaService { } if (hasNextEndpoint && (status === 429 || (isNumber(status) && status >= 500))) { - logger.warn( - `Quota API ${endpoint} returned ${status}, falling back to next endpoint`, - ); + logger.warn(`Quota API ${endpoint} returned ${status}, falling back to next endpoint`); lastError = new Error(`HTTP ${status} - ${text}`); await new Promise((r) => setTimeout(r, 1000)); continue; diff --git a/src/lib/geminiNexus/types.ts b/src/lib/geminiNexus/types.ts index de114ae..2953e7b 100644 --- a/src/lib/geminiNexus/types.ts +++ b/src/lib/geminiNexus/types.ts @@ -53,12 +53,15 @@ export interface ClaudeRequest { tools?: Tool[]; stream?: boolean; max_tokens?: number; + max_completion_tokens?: number; stop_sequences?: string[]; temperature?: number; top_p?: number; top_k?: number; thinking?: ThinkingConfig; metadata?: Metadata; + tool_choice?: 'auto' | 'none' | { type: 'function'; function: { name: string } }; + response_format?: { type: 'text' | 'json_object' | 'json_schema'; json_schema?: any }; } export interface ThinkingConfig { @@ -77,6 +80,7 @@ export interface SystemBlock { export interface Message { role: string; content: string | ContentBlock[]; + name?: string; } export type ContentBlock = @@ -185,6 +189,8 @@ export interface GenerationConfig { responseModalities?: string[]; /** Image generation configuration */ imageConfig?: ImageConfig; + /** Response schema for structured output */ + responseSchema?: unknown; } /** diff --git a/src/localization/i18n.ts b/src/localization/i18n.ts index 3ebf1a0..cfb00bb 100644 --- a/src/localization/i18n.ts +++ b/src/localization/i18n.ts @@ -154,7 +154,8 @@ i18n timeout: 'Request Timeout (Seconds)', accountProxies: { title: 'Account Proxies', - description: 'Configure specific proxies for individual accounts. These will override the global upstream proxy for that specific account.', + description: + 'Configure specific proxies for individual accounts. These will override the global upstream proxy for that specific account.', }, }, modelMapping: { @@ -1109,7 +1110,8 @@ i18n timeout: 'Тайм-аут запроса (сек)', accountProxies: { title: 'Прокси аккаунтов', - description: 'Настройка индивидуальных прокси для каждого аккаунта. Эти настройки переопределят глобальный прокси для данного аккаунта.', + description: + 'Настройка индивидуальных прокси для каждого аккаунта. Эти настройки переопределят глобальный прокси для данного аккаунта.', }, }, modelMapping: { diff --git a/src/localization/vi.ts b/src/localization/vi.ts index 14385ed..4ebe5e6 100644 --- a/src/localization/vi.ts +++ b/src/localization/vi.ts @@ -134,7 +134,8 @@ const vi = { timeout: 'Thời gian chờ request (giây)', accountProxies: { title: 'Proxy tài khoản', - description: 'Cấu hình proxy riêng cho từng tài khoản. Những cấu hình này sẽ ghi đè proxy trung gian chung cho tài khoản đó.', + description: + 'Cấu hình proxy riêng cho từng tài khoản. Những cấu hình này sẽ ghi đè proxy trung gian chung cho tài khoản đó.', }, }, modelMapping: { diff --git a/src/main.ts b/src/main.ts index d8298fd..2f82b59 100644 --- a/src/main.ts +++ b/src/main.ts @@ -468,9 +468,7 @@ app logger.info('Startup: Auto-Switch enabled, starting monitor...'); CloudMonitorService.start(); } else { - logger.info( - 'Startup: Auto-Switch disabled, running one-time quota and AI credits sync...', - ); + logger.info('Startup: Auto-Switch disabled, running one-time quota and AI credits sync...'); await CloudMonitorService.poll(); } } catch (e) { diff --git a/src/routes/proxy.tsx b/src/routes/proxy.tsx index d0aeb35..06d31c2 100644 --- a/src/routes/proxy.tsx +++ b/src/routes/proxy.tsx @@ -24,15 +24,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { - Loader2, - Copy, - CheckCircle, - Code, - Terminal, - Eye, - EyeOff, -} from 'lucide-react'; +import { Loader2, Copy, CheckCircle, Code, Terminal, Eye, EyeOff } from 'lucide-react'; import { Dialog, DialogContent, @@ -180,14 +172,19 @@ function ProxyPage() { OpenAI: [], Other: [], }; - + availableModelIds.forEach((id) => { const lower = id.toLowerCase(); if (lower.includes('gemini') || lower.includes('learnlm') || lower.includes('gemma')) { groups['Google'].push(id); } else if (lower.includes('claude')) { groups['Anthropic'].push(id); - } else if (lower.includes('gpt') || lower.includes('o1') || lower.includes('o3') || lower.includes('dall-e')) { + } else if ( + lower.includes('gpt') || + lower.includes('o1') || + lower.includes('o3') || + lower.includes('dall-e') + ) { groups['OpenAI'].push(id); } else { groups['Other'].push(id); @@ -634,7 +631,10 @@ print(response.choices[0].message.content)`;