diff --git a/changelog/unreleased/qoder-model-context-window.md b/changelog/unreleased/qoder-model-context-window.md new file mode 100644 index 00000000..e1af6eb7 --- /dev/null +++ b/changelog/unreleased/qoder-model-context-window.md @@ -0,0 +1,7 @@ +### English + +- Present Qoder's context window like Trae's Max-context switch. Qoder reports a default window and a larger selectable window per model but has no upstream toggle, so the console now shows the same "default → larger + switch" control it uses for Trae instead of a raw number input. Turning it on sends the model's largest window as the request's `context_length`; turning it off clears the override so requests fall back to the model's default. The window values come from Qoder's own catalog (`default_context_window` / `available_context_windows`) rather than a hardcoded 180000, so glm-5.3-flash shows 1M instead of being capped at 180k. Qoder Global and CN. + +### 中文 + +- Qoder 的上下文窗口改为按 Trae 的「更大上下文」开关呈现。Qoder 每个模型都会上报默认窗口和一个可选更大窗口,但上游没有开关字段,因此控制台不再用裸数字输入框,而是复用 Trae 同款的「默认档 → 更大档 + 开关」。开启时把该模型的最大窗口作为请求的 `context_length` 发出;关闭时清除覆盖值,请求回落到模型默认窗口。窗口数值来自 Qoder 自身目录(`default_context_window` / `available_context_windows`),不再是硬编码的 180000,因此 glm-5.3-flash 显示 1M 而不再被压到 180k。国际版与国内版均适用。 diff --git a/frontend/src/api/overview.ts b/frontend/src/api/overview.ts index e270e7ee..6d011a14 100644 --- a/frontend/src/api/overview.ts +++ b/frontend/src/api/overview.ts @@ -106,15 +106,26 @@ export function updateModelContext(modelKey: string, contextLength: number) { } export function updateTraeMaxMode(modelKey: string, maxMode: boolean) { + return updateProviderMaxMode('trae', modelKey, maxMode) +} + +export function updateProviderMaxMode(provider: string, modelKey: string, maxMode: boolean, contextWindow?: number | null) { return api<{ model: string provider: string max_mode: boolean reasoning_effort?: string context_custom: boolean - }>(`/api/models/trae/${encodeURIComponent(modelKey)}`, { + context_length?: number + default_context_length?: number + }>(`/api/models/${encodeURIComponent(provider)}/${encodeURIComponent(modelKey)}`, { method: 'PATCH', - body: JSON.stringify({ max_mode: maxMode }), + // Qoder has no is_max_mode upstream: its toggle maps onto the numeric + // window, so send the target window explicitly. Trae ignores context_length + // and switches on max_mode alone. + body: JSON.stringify( + provider === 'qoder' && maxMode && contextWindow ? { max_mode: maxMode, context_length: contextWindow } : { max_mode: maxMode }, + ), }) } diff --git a/frontend/src/components/ModelDetailsModal.tsx b/frontend/src/components/ModelDetailsModal.tsx index edf647e7..67b7cee5 100644 --- a/frontend/src/components/ModelDetailsModal.tsx +++ b/frontend/src/components/ModelDetailsModal.tsx @@ -56,7 +56,7 @@ export function ModelDetailsModal({ model, t, onClose }: Props) {
{t('contextWindowCol')}
{formatTokens(windowDev)}{windowMax && windowMax !== windowDev ? ` → ${formatTokens(windowMax)}` : ''}
- {model.supports_max_mode ?

{t('maxModeHint')}

: null} + {model.supports_max_mode ?

{provider === 'qoder' ? t('qoderMaxModeHint') : t('maxModeHint')}

: null} {!model.supports_max_mode && windowMax && windowMax !== windowDev ?

{t('workbuddyContextHint')}

: null} {traeTier ? (traeTier.prompt_max_tokens ?
{t('promptMaxTokens')}: {formatTokens(traeTier.prompt_max_tokens)}
: null) diff --git a/frontend/src/i18n/messages.ts b/frontend/src/i18n/messages.ts index eddabd15..8b26b748 100644 --- a/frontend/src/i18n/messages.ts +++ b/frontend/src/i18n/messages.ts @@ -427,6 +427,7 @@ export const messages: Record = { contextMaxUnavailable: 'This model has no Max context switch.', maxMode: 'Max context', maxModeHint: 'Trae larger context window, often 200k to 1M. Not a plan or quota. Used only when the request omits is_max_mode; an explicit request still wins. Unsupported models never send this switch.', + qoderMaxModeHint: 'Qoder larger context window. Not a plan or quota. When on, requests use the model’s largest window; when off (or when a request sets its own context) the default window applies.', catalogWindow: 'Catalog size', catalogWindowHint: 'This Trae model has no Max-context switch. The number is the catalog window.', workbuddyContextHint: 'WorkBuddy desktop default and optional context budgets from the catalog. This is a local compact budget, not Trae is_max_mode. Chat requests do not send a window switch.', @@ -1077,6 +1078,7 @@ export const messages: Record = { contextMaxUnavailable: '这个模型没有更大上下文开关。', maxMode: '更大上下文', maxModeHint: 'Trae 的更大上下文窗口,常见是 200k 提到 1M,不是套餐也不是额度。只在请求没带 is_max_mode 时使用;请求里写了仍以请求为准。模型不支持时不会发送这个开关。', + qoderMaxModeHint: 'Qoder 的更大上下文窗口,不是套餐也不是额度。开启时请求使用该模型的最大窗口;关闭时(或请求自带上下文窗口时)使用默认窗口。', catalogWindow: '目录默认窗口', catalogWindowHint: '这个 Trae 模型没有更大上下文开关,数字是目录里的默认窗口。', workbuddyContextHint: 'WorkBuddy 桌面端目录里的默认窗口和可选窗口,用于本地压缩预算,不是 Trae 的 is_max_mode。聊天请求不会发送窗口开关。', diff --git a/frontend/src/pages/ProvidersPage.tsx b/frontend/src/pages/ProvidersPage.tsx index 854f9e98..064fb320 100644 --- a/frontend/src/pages/ProvidersPage.tsx +++ b/frontend/src/pages/ProvidersPage.tsx @@ -1,9 +1,9 @@ import { useEffect, useMemo, useState } from 'react' -import { Button, Card, Chip, Input, Table, Tooltip } from '@heroui/react' -import { Cube, ArrowClockwise, ArrowCounterClockwise, FloppyDisk, MagnifyingGlass, Info } from '@phosphor-icons/react' +import { Button, Card, Chip, Table, Tooltip } from '@heroui/react' +import { Cube, ArrowClockwise, MagnifyingGlass, Info } from '@phosphor-icons/react' import { useI18n } from '@/hooks/useI18n' import { useOverview } from '@/hooks/useOverview' -import { fetchModelsCached, fetchProviders, refreshModels, updateModelContext, updateProviderReasoning, updateTraeMaxMode, type ProviderDescriptor } from '@/api/overview' +import { fetchModelsCached, fetchProviders, refreshModels, updateProviderMaxMode, updateProviderReasoning, type ProviderDescriptor } from '@/api/overview' import type { Overview } from '@/api/types' import { ProviderMark } from '@/components/ProviderMark' import { ModelDetailsModal, formatTokens } from '@/components/ModelDetailsModal' @@ -84,40 +84,18 @@ function HintLabel({ label, hint }: { label: string; hint: string }) { function ModelContextControls({ model, - drafts, saving, t, - onDraft, onToggleTraeMax, onReasoningChange, }: { model: ModelInfo - drafts: Record saving: boolean t: Translate - onDraft: (key: string, value: string) => void onToggleTraeMax: (model: ModelInfo, selected: boolean) => void onReasoningChange: (model: ModelInfo, next: string) => void }) { - const key = modelSettingsKey(model) const provider = modelProvider(model) - if (provider === 'qoder') { - return ( -
- onDraft(key, event.target.value)} - aria-label={`${model.id} ${t('contextWindowCol')}`} - /> - -
- ) - } return (
@@ -129,7 +107,7 @@ function ModelContextControls({ {provider === 'workbuddy' && model.catalog_context_length_max && model.catalog_context_length_max !== (model.catalog_context_length || model.context_length) ? ( ) : null} - {provider === 'trae' && model.supports_max_mode ? ( + {(provider === 'trae' || provider === 'qoder') && model.supports_max_mode ? (
) : null} @@ -167,31 +145,13 @@ function ModelContextControls({ function ModelActions({ model, - saving, t, - onSave, - onReset, onDetails, }: { model: ModelInfo - saving: boolean t: Translate - onSave: (model: ModelInfo) => void - onReset: (model: ModelInfo) => void onDetails: (model: ModelInfo) => void }) { - if (modelProvider(model) === 'qoder') { - return ( -
- - -
- ) - } return (