From 3e1c9b3cdfc14576366d94372c3203bbb587fe9b Mon Sep 17 00:00:00 2001 From: chenwangnec Date: Fri, 10 Apr 2026 21:48:20 +0800 Subject: [PATCH 1/4] 20260410 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 20260410启动时边框时有时无"问题的原因和修复方案 --- docs/ui-rendering.md | 89 +++++++++++++++++++++++++ src/components/LogoV2/CondensedLogo.tsx | 65 +++++++++++------- src/components/LogoV2/LogoV2.tsx | 18 ++--- 3 files changed, 134 insertions(+), 38 deletions(-) create mode 100644 docs/ui-rendering.md diff --git a/docs/ui-rendering.md b/docs/ui-rendering.md new file mode 100644 index 000000000..fea226286 --- /dev/null +++ b/docs/ui-rendering.md @@ -0,0 +1,89 @@ +# UI 渲染与界面层 + +> 记录 Claude Code 终端 UI(Ink 框架)的渲染行为、布局模式、常见问题及修复。 + +## LogoV2 启动欢迎界面 + +### 组件结构 + +``` +LogoV2.tsx (src/components/LogoV2/) +├── CondensedLogo.tsx — 缩略模式(无边框,日常最常见) +├── LogoV2.tsx — 完整模式(有边框)/ 紧凑模式(有边框) +├── CondensedLogo.tsx — 缩略模式(无边框,日常最常见) +├── Clawd.tsx — ASCII 猫吉祥物 +├── AnimatedClawd.tsx — 动画版 Clawd +├── FeedColumn.tsx — 右侧信息流(活动记录/更新日志) +└── WelcomeV2.tsx — 首次 Onboarding 欢迎文本 +``` + +### 三种渲染模式 + +| 模式 | 触发条件 | 边框 | 说明 | +|------|---------|------|------| +| **缩略模式** (Condensed) | 无 Release Notes 且无首次引导 且未强制 | ❌ | 日常启动最常见,仅显示 Clawd + 版本信息 | +| **紧凑模式** (Compact) | 终端宽度 < 70 列 | ✅ | 紫色圆角边框,内容居中 | +| **完整模式** (Full) | 有 Release Notes 或首次引导或强制 | ✅ | 紫色圆角边框,双栏布局 + 信息流 | + +### 模式判断逻辑(源码:`src/components/LogoV2/LogoV2.tsx`) + +```typescript +// 原始逻辑:三种模式切换 +const isCondensedMode = + !hasReleaseNotes && + !showOnboarding && + !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO) + +// 环境变量强制:始终显示完整模式 +// CLAUDE_CODE_FORCE_FULL_LOGO=1 bun run dev +``` + +**CondensedLogo 内部**(`src/components/LogoV2/CondensedLogo.tsx`):无任何边框,纯文本 + ASCII 猫。 + +### 边框渲染机制 + +边框由 `@anthropic/ink` 的 `Box` 组件通过 `cli-boxes` 库绘制: + +- `borderStyle="round"` → 使用 `cli-boxes` 的 `round` 样式(`╭─╮` `│` `╰─╯`) +- `borderColor="claude"` → 紫色主题色 +- `borderText` → 边框顶部嵌入标题文本 "Claude Code" + +详见 `packages/@ant/ink/src/core/render-border.ts`。 + +## 常见问题 + +### 问题:启动时欢迎边框时有时无 + +**原因**:日常启动(无 Release Notes、非首次使用)走缩略模式,不显示边框。 + +**修复**:在 `LogoV2.tsx` 中永久关闭缩略模式: + +```diff +- const isCondensedMode = +- !hasReleaseNotes && +- !showOnboarding && +- !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO) ++ const isCondensedMode = false + +- if ( +- !hasReleaseNotes && +- !showOnboarding && +- !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO) +- ) { ++ if (false) { + return + } +``` + +修改后,无论何种情况启动,都会显示完整模式的紫色圆角边框。 + +**影响**: +- 每次启动都会渲染完整的边框 + 信息流(活动记录或更新日志) +- 轻微增加启动渲染开销(从 0 边框到有边框双栏) +- 不再走 CondensedLogo(节省了 GuestPassesUpsell 等副作用计数逻辑) + +### 问题:终端宽度不足导致边框变形 + +**原因**:`getLayoutMode(columns)` 在终端 < 70 列时切换到 compact 模式。 + +**参考**:`src/utils/logoV2Utils.ts` — `getLayoutMode(columns: number): LayoutMode` diff --git a/src/components/LogoV2/CondensedLogo.tsx b/src/components/LogoV2/CondensedLogo.tsx index eb048ec2d..1e159c92a 100644 --- a/src/components/LogoV2/CondensedLogo.tsx +++ b/src/components/LogoV2/CondensedLogo.tsx @@ -79,39 +79,56 @@ export function CondensedLogo(): ReactNode { : textWidth const truncatedCwd = truncatePath(cwd, Math.max(cwdAvailableWidth, 10)) + const userTheme = resolveThemeSetting(getGlobalConfig().theme) + const borderTitle = color('claude', userTheme)(' Claude Code ') + // OffscreenFreeze: the logo sits at the top of the message list and is the // first thing to enter scrollback. useMainLoopModel() subscribes to model // changes and getLogoDisplayData() reads getCwd()/subscription state — any // of which changing while in scrollback would force a full terminal reset. return ( - - {isFullscreenEnvEnabled() ? : } + + + {isFullscreenEnvEnabled() ? : } - {/* Info */} - - - Claude Code{' '} - v{truncatedVersion} - - {shouldSplit ? ( - <> - {truncatedModel} - {truncatedBilling} - - ) : ( + {/* Info */} + + + Claude Code{' '} + v{truncatedVersion} + + {shouldSplit ? ( + <> + {truncatedModel} + {truncatedBilling} + + ) : ( + + {truncatedModel} · {truncatedBilling} + + )} - {truncatedModel} · {truncatedBilling} + {agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd} - )} - - {agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd} - - {showGuestPassesUpsell && } - {!showGuestPassesUpsell && showOverageCreditUpsell && ( - - )} - + {showGuestPassesUpsell && } + {!showGuestPassesUpsell && showOverageCreditUpsell && ( + + )} + + ) diff --git a/src/components/LogoV2/LogoV2.tsx b/src/components/LogoV2/LogoV2.tsx index c7dcf4139..3f8048b44 100644 --- a/src/components/LogoV2/LogoV2.tsx +++ b/src/components/LogoV2/LogoV2.tsx @@ -40,7 +40,6 @@ import { CondensedLogo } from './CondensedLogo.js' import { OffscreenFreeze } from '../OffscreenFreeze.js' import { checkForReleaseNotesSync } from '../../utils/releaseNotes.js' import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js' -import { isEnvTruthy } from 'src/utils/envUtils.js' import { getStartupPerfLogPath, isDetailedProfilingEnabled, @@ -130,13 +129,8 @@ export function LogoV2(): React.ReactNode { } }, [config, showOnboarding]) - // In condensed mode (early-return below renders ), - // CondensedLogo's own useEffect handles the impression count. Skipping - // here avoids double-counting since hooks fire before the early return. - const isCondensedMode = - !hasReleaseNotes && - !showOnboarding && - !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO) + // Always show full logo with border (force full logo permanently enabled) + const isCondensedMode = false useEffect(() => { if (showGuestPassesUpsell && !showOnboarding && !isCondensedMode) { @@ -177,12 +171,8 @@ export function LogoV2(): React.ReactNode { LEFT_PANEL_MAX_WIDTH - 20, ) - // Show condensed logo if no new changelog and not showing onboarding and not forcing full logo - if ( - !hasReleaseNotes && - !showOnboarding && - !isEnvTruthy(process.env.CLAUDE_CODE_FORCE_FULL_LOGO) - ) { + // Show full logo with border (force full logo permanently enabled) + if (false) { return ( <> From 8aba10a0d79c1e491f556303dbb6df414eea56cd Mon Sep 17 00:00:00 2001 From: chenwangnec Date: Fri, 10 Apr 2026 22:03:37 +0800 Subject: [PATCH 2/4] =?UTF-8?q?202604102203No=20recent=20activity"=20?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E7=9A=84=E5=8E=9F=E5=9B=A0=E5=88=86=E6=9E=90?= =?UTF-8?q?=E5=92=8C=E4=BF=AE=E5=A4=8D=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No recent activity" 问题的原因分析和修复记录 --- docs/ui-rendering.md | 36 ++++++++++++++++++++++++++++++++++-- src/setup.ts | 10 ++++++---- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/ui-rendering.md b/docs/ui-rendering.md index fea226286..51e5779f9 100644 --- a/docs/ui-rendering.md +++ b/docs/ui-rendering.md @@ -21,9 +21,11 @@ LogoV2.tsx (src/components/LogoV2/) | 模式 | 触发条件 | 边框 | 说明 | |------|---------|------|------| -| **缩略模式** (Condensed) | 无 Release Notes 且无首次引导 且未强制 | ❌ | 日常启动最常见,仅显示 Clawd + 版本信息 | +| **缩略模式** (Condensed) | ~~无 Release Notes 且无首次引导~~ | ❌ | **已永久关闭**(2026-04-10),不再进入 | | **紧凑模式** (Compact) | 终端宽度 < 70 列 | ✅ | 紫色圆角边框,内容居中 | -| **完整模式** (Full) | 有 Release Notes 或首次引导或强制 | ✅ | 紫色圆角边框,双栏布局 + 信息流 | +| **完整模式** (Full) | 默认路径 | ✅ | 紫色圆角边框,双栏布局 + 信息流 | + +> **注**:缩略模式已在 `LogoV2.tsx` 中永久关闭(`isCondensedMode = false`),所有启动都走完整模式或紧凑模式。 ### 模式判断逻辑(源码:`src/components/LogoV2/LogoV2.tsx`) @@ -87,3 +89,33 @@ const isCondensedMode = **原因**:`getLayoutMode(columns)` 在终端 < 70 列时切换到 compact 模式。 **参考**:`src/utils/logoV2Utils.ts` — `getLayoutMode(columns: number): LayoutMode` + +### 问题:Recent Activity 显示 "No recent activity" + +**原因**:`src/setup.ts` 中 `getRecentActivity()` 只在 `hasReleaseNotes` 为 true 时才调用。日常启动(无新 Release Notes)时,`cachedActivity` 始终为空数组。 + +**修复**(`src/setup.ts` 第 383-395 行): + +```diff + if (!isBareMode()) { +- const { hasReleaseNotes } = await checkForReleaseNotes( +- getGlobalConfig().lastReleaseNotesSeen, +- ) +- if (hasReleaseNotes) { +- await getRecentActivity() +- } ++ // Populate release notes cache (side effect: fetches changelog if needed) ++ void checkForReleaseNotes(getGlobalConfig().lastReleaseNotesSeen) ++ // Load recent activity unconditionally (not tied to release notes) ++ try { ++ await getRecentActivity() ++ } catch (error) { ++ logError('Failed to load recent activity:', error) ++ } + } +``` + +**关键变化**: +1. `getRecentActivity()` 从 `if (hasReleaseNotes)` 块中移出,无条件调用 +2. 增加 try-catch 防止损坏的会话文件阻塞启动 +3. `checkForReleaseNotes` 改为 `void` 调用(保留 cache 填充的副作用,但不阻塞等待结果) diff --git a/src/setup.ts b/src/setup.ts index 985e8577a..f4977e555 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -384,11 +384,13 @@ export async function setup( // --bare / SIMPLE: skip — release notes are interactive-UI display data, // and getRecentActivity() reads up to 10 session JSONL files. if (!isBareMode()) { - const { hasReleaseNotes } = await checkForReleaseNotes( - getGlobalConfig().lastReleaseNotesSeen, - ) - if (hasReleaseNotes) { + // Populate release notes cache (side effect: fetches changelog if needed) + void checkForReleaseNotes(getGlobalConfig().lastReleaseNotesSeen) + // Load recent activity unconditionally (not tied to release notes) + try { await getRecentActivity() + } catch (error) { + logError('Failed to load recent activity:', error) } } From db73af0fbbd80bb903fbe8f726947a6878a4a91e Mon Sep 17 00:00:00 2001 From: chenwangnec Date: Sat, 11 Apr 2026 15:08:04 +0800 Subject: [PATCH 3/4] =?UTF-8?q?202604111507=E9=85=8D=E7=BD=AE=E9=98=BF?= =?UTF-8?q?=E9=87=8C=E4=BA=91=E7=99=BE=E7=82=BC=20Anthropic=20=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E7=AB=AF=E7=82=B9=E3=80=82=E5=B7=B2=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=98=A0=E5=B0=84=20Claude=20=E4=B8=89=E5=A4=A7=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=AE=B6=E6=97=8F=E5=88=B0=E7=99=BE=E7=82=BC=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 配置阿里云百炼 Anthropic 兼容端点。已自动映射 Claude 三大模型家族到百炼模型 --- src/components/ConsoleOAuthFlow.tsx | 284 ++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx index bd1dd5d1e..689a715c5 100644 --- a/src/components/ConsoleOAuthFlow.tsx +++ b/src/components/ConsoleOAuthFlow.tsx @@ -55,6 +55,15 @@ type OAuthStatus = opusModel: string activeField: 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model' } // Gemini Generate Content API platform + | { + state: 'alibaba_coding_plan' + baseUrl: string + apiKey: string + opusModel: string + sonnetModel: string + haikuModel: string + activeField: 'api_key' | 'base_url' | 'opus_model' | 'sonnet_model' | 'haiku_model' + } // Alibaba Cloud Bailian Coding Plan (OpenAI compatible) | { state: 'ready_to_start' } // Flow started, waiting for browser to open | { state: 'waiting_for_login'; url: string } // Browser opened, waiting for user to login | { state: 'creating_api_key' } // Got access token, creating API key @@ -485,6 +494,16 @@ function OAuthStatusMessage({ ), value: 'gemini_api', }, + { + label: ( + + 阿里云百炼 Coding Plan ·{' '} + Anthropic 兼容 API + {'\n'} + + ), + value: 'alibaba_coding_plan', + }, { label: ( @@ -563,6 +582,17 @@ function OAuthStatusMessage({ opusModel: process.env.GEMINI_DEFAULT_OPUS_MODEL ?? '', activeField: 'base_url', }) + } else if (value === 'alibaba_coding_plan') { + logEvent('tengu_alibaba_coding_plan_selected', {}) + setOAuthStatus({ + state: 'alibaba_coding_plan', + baseUrl: process.env.ANTHROPIC_BASE_URL ?? 'https://coding.dashscope.aliyuncs.com/apps/anthropic', + apiKey: process.env.ANTHROPIC_AUTH_TOKEN ?? '', + opusModel: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL ?? 'qwen3-max-2026-01-23', + sonnetModel: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL ?? 'qwen3.6-plus', + haikuModel: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL ?? 'glm-5', + activeField: 'api_key', + }) } else if (value === 'platform') { logEvent('tengu_oauth_platform_selected', {}) setOAuthStatus({ state: 'platform_setup' }) @@ -1275,6 +1305,260 @@ function OAuthStatusMessage({ ) } + case 'alibaba_coding_plan': + { + type AlibabaField = 'api_key' | 'base_url' | 'opus_model' | 'sonnet_model' | 'haiku_model' + const ALIBABA_FIELDS: AlibabaField[] = [ + 'api_key', + 'base_url', + 'opus_model', + 'sonnet_model', + 'haiku_model', + ] + + // 阿里云百炼模型映射(Claude 家族 → 百炼模型) + const BAILIAN_MODEL_MAP = { + opus: ['qwen3-max-2026-01-23', 'qwen3.6-plus'], + sonnet: ['qwen3.6-plus', 'qwen3-coder-next', 'kimi-k2.5'], + haiku: ['glm-5', 'MiniMax-M2.5', 'qwen3-coder-plus'], + } + + const al = oauthStatus as { + state: 'alibaba_coding_plan' + activeField: AlibabaField + baseUrl: string + apiKey: string + opusModel: string + sonnetModel: string + haikuModel: string + } + const { activeField, baseUrl, apiKey, opusModel, sonnetModel, haikuModel } = al + const alibabaDisplayValues: Record = { + api_key: apiKey, + base_url: baseUrl, + opus_model: opusModel, + sonnet_model: sonnetModel, + haiku_model: haikuModel, + } + + const [alibabaInputValue, setAlibabaInputValue] = useState(() => alibabaDisplayValues[activeField]) + const [alibabaInputCursorOffset, setAlibabaInputCursorOffset] = useState( + () => alibabaDisplayValues[activeField].length, + ) + + const buildAlibabaState = useCallback( + (field: AlibabaField, value: string, newActive?: AlibabaField) => { + const s = { + state: 'alibaba_coding_plan' as const, + activeField: newActive ?? activeField, + baseUrl, + apiKey, + opusModel, + sonnetModel, + haikuModel, + } + switch (field) { + case 'api_key': + return { ...s, apiKey: value } + case 'base_url': + return { ...s, baseUrl: value } + case 'opus_model': + return { ...s, opusModel: value } + case 'sonnet_model': + return { ...s, sonnetModel: value } + case 'haiku_model': + return { ...s, haikuModel: value } + } + }, + [activeField, baseUrl, apiKey, opusModel, sonnetModel, haikuModel], + ) + + const switchAlibabaField = useCallback( + (target: AlibabaField) => { + setOAuthStatus(buildAlibabaState(activeField, alibabaInputValue, target)) + setAlibabaInputValue(alibabaDisplayValues[target] ?? '') + setAlibabaInputCursorOffset((alibabaDisplayValues[target] ?? '').length) + }, + [activeField, alibabaInputValue, alibabaDisplayValues, buildAlibabaState, setOAuthStatus], + ) + + const doAlibabaSave = useCallback(() => { + const finalVals = { ...alibabaDisplayValues, [activeField]: alibabaInputValue } + const env: Record = {} + + if (finalVals.base_url) { + try { + new URL(finalVals.base_url) + } catch { + setOAuthStatus({ + state: 'error', + message: '无效的 Base URL:请输入完整的 URL(如 https://coding.dashscope.aliyuncs.com/apps/anthropic)', + toRetry: { + state: 'alibaba_coding_plan', + baseUrl: '', + apiKey: '', + opusModel: finalVals.opus_model, + sonnetModel: finalVals.sonnet_model, + haikuModel: finalVals.haiku_model, + activeField: 'base_url', + }, + }) + return + } + env.ANTHROPIC_BASE_URL = finalVals.base_url + } + + if (finalVals.api_key) env.ANTHROPIC_AUTH_TOKEN = finalVals.api_key + if (finalVals.opus_model) env.ANTHROPIC_DEFAULT_OPUS_MODEL = finalVals.opus_model + if (finalVals.sonnet_model) env.ANTHROPIC_DEFAULT_SONNET_MODEL = finalVals.sonnet_model + if (finalVals.haiku_model) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = finalVals.haiku_model + + const { error } = updateSettingsForSource('userSettings', { + modelType: 'anthropic' as any, + env, + } as any) + if (error) { + setOAuthStatus({ + state: 'error', + message: '保存设置失败,请重试。', + toRetry: { + state: 'alibaba_coding_plan', + baseUrl: finalVals.base_url ?? '', + apiKey: finalVals.api_key ?? '', + opusModel: finalVals.opus_model ?? 'qwen3-max-2026-01-23', + sonnetModel: finalVals.sonnet_model ?? 'qwen3.6-plus', + haikuModel: finalVals.haiku_model ?? 'glm-5', + activeField: 'api_key', + }, + }) + } else { + for (const [k, v] of Object.entries(env)) process.env[k] = v + setOAuthStatus({ state: 'success' }) + void onDone() + } + }, [activeField, alibabaInputValue, alibabaDisplayValues, setOAuthStatus, onDone]) + + const handleAlibabaEnter = useCallback(() => { + const idx = ALIBABA_FIELDS.indexOf(activeField) + if (idx === ALIBABA_FIELDS.length - 1) { + setOAuthStatus(buildAlibabaState(activeField, alibabaInputValue)) + doAlibabaSave() + } else { + const next = ALIBABA_FIELDS[idx + 1]! + setOAuthStatus(buildAlibabaState(activeField, alibabaInputValue, next)) + setAlibabaInputValue(alibabaDisplayValues[next] ?? '') + setAlibabaInputCursorOffset((alibabaDisplayValues[next] ?? '').length) + } + }, [activeField, alibabaInputValue, buildAlibabaState, doAlibabaSave, alibabaDisplayValues, setOAuthStatus]) + + useKeybinding( + 'tabs:next', + () => { + const idx = ALIBABA_FIELDS.indexOf(activeField) + if (idx < ALIBABA_FIELDS.length - 1) { + setOAuthStatus(buildAlibabaState(activeField, alibabaInputValue, ALIBABA_FIELDS[idx + 1])) + setAlibabaInputValue(alibabaDisplayValues[ALIBABA_FIELDS[idx + 1]!] ?? '') + setAlibabaInputCursorOffset((alibabaDisplayValues[ALIBABA_FIELDS[idx + 1]!] ?? '').length) + } + }, + { context: 'FormField' }, + ) + useKeybinding( + 'tabs:previous', + () => { + const idx = ALIBABA_FIELDS.indexOf(activeField) + if (idx > 0) { + setOAuthStatus(buildAlibabaState(activeField, alibabaInputValue, ALIBABA_FIELDS[idx - 1])) + setAlibabaInputValue(alibabaDisplayValues[ALIBABA_FIELDS[idx - 1]!] ?? '') + setAlibabaInputCursorOffset((alibabaDisplayValues[ALIBABA_FIELDS[idx - 1]!] ?? '').length) + } + }, + { context: 'FormField' }, + ) + useKeybinding( + 'confirm:no', + () => { + setOAuthStatus({ state: 'idle' }) + }, + { context: 'Confirmation' }, + ) + + const alibabaColumns = useTerminalSize().columns - 20 + + const renderAlibabaRow = ( + field: AlibabaField, + label: string, + opts?: { mask?: boolean }, + ) => { + const active = activeField === field + const val = alibabaDisplayValues[field] + return ( + + + {` ${label} `} + + + {active ? ( + + + + + ) : val ? ( + + {opts?.mask + ? val.slice(0, 8) + '\u00b7'.repeat(Math.max(0, val.length - 8)) + : val} + + ) : ( + (按 Enter 输入) + )} + + ) + } + + const borderLine = (active: boolean) => ( + {'─'.repeat(alibabaColumns + 12)} + ) + + return ( + + 阿里云百炼 Coding Plan + + 配置阿里云百炼 Anthropic 兼容端点。已自动映射 Claude 三大模型家族到百炼模型。 + + + {renderAlibabaRow('api_key', 'API 密钥', { mask: true })} + {borderLine(activeField === 'api_key')} + {renderAlibabaRow('base_url', 'Base URL')} + {borderLine(activeField === 'base_url')} + {renderAlibabaRow('opus_model', 'Opus (最强推理)')} + {borderLine(activeField === 'opus_model')} + {renderAlibabaRow('sonnet_model', 'Sonnet(均衡通用)')} + {borderLine(activeField === 'sonnet_model')} + {renderAlibabaRow('haiku_model', 'Haiku (快速轻量)')} + + + 可用模型:qwen3.6-plus · qwen3.5-plus · qwen3-max · qwen3-coder-next · qwen3-coder-plus · glm-5 · glm-4.7 · kimi-k2.5 · MiniMax-M2.5 + + + ↑↓/Tab 切换字段 · 最后一字段按 Enter 保存 · Esc 返回 + + + ) + } + case 'platform_setup': return ( From 879165e86b212466af7ab01fd0caf98811263f0e Mon Sep 17 00:00:00 2001 From: chenwangnec Date: Sun, 12 Apr 2026 20:33:05 +0800 Subject: [PATCH 4/4] 202604122033 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qwen3.6-plus 1M的上下文窗口 7 个 TypeScript 类型错误已修复 --- src/components/ConsoleOAuthFlow.tsx | 6 ++++-- src/components/LogoV2/CondensedLogo.tsx | 4 +++- src/components/LogoV2/LogoV2.tsx | 17 ++++++++++------- src/setup.ts | 2 +- src/utils/context.ts | 15 ++++++++++++++- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx index 689a715c5..091428c96 100644 --- a/src/components/ConsoleOAuthFlow.tsx +++ b/src/components/ConsoleOAuthFlow.tsx @@ -1513,7 +1513,7 @@ function OAuthStatusMessage({ mask={opts?.mask ? '*' : undefined} focus={true} /> - + ) : val ? ( @@ -1529,7 +1529,9 @@ function OAuthStatusMessage({ } const borderLine = (active: boolean) => ( - {'─'.repeat(alibabaColumns + 12)} + active + ? {'─'.repeat(alibabaColumns + 12)} + : {'─'.repeat(alibabaColumns + 12)} ) return ( diff --git a/src/components/LogoV2/CondensedLogo.tsx b/src/components/LogoV2/CondensedLogo.tsx index 1e159c92a..b815f0925 100644 --- a/src/components/LogoV2/CondensedLogo.tsx +++ b/src/components/LogoV2/CondensedLogo.tsx @@ -2,7 +2,9 @@ import * as React from 'react' import { type ReactNode, useEffect } from 'react' import { useMainLoopModel } from '../../hooks/useMainLoopModel.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' -import { Box, Text, stringWidth } from '@anthropic/ink' +import { Box, Text, color, stringWidth } from '@anthropic/ink' +import { getGlobalConfig } from 'src/utils/config.js' +import { resolveThemeSetting } from 'src/utils/systemTheme.js' import { useAppState } from '../../state/AppState.js' import { getEffortSuffix } from '../../utils/effort.js' import { truncate } from '../../utils/format.js' diff --git a/src/components/LogoV2/LogoV2.tsx b/src/components/LogoV2/LogoV2.tsx index 3f8048b44..e50c41573 100644 --- a/src/components/LogoV2/LogoV2.tsx +++ b/src/components/LogoV2/LogoV2.tsx @@ -62,6 +62,9 @@ const ChannelsNoticeModule = ? (require('./ChannelsNotice.js') as typeof import('./ChannelsNotice.js')) : null /* eslint-enable @typescript-eslint/no-require-imports */ + +// Extract for safe JSX rendering (avoids TS18047 narrowing issue in JSX) +const ChannelsNotice = ChannelsNoticeModule?.ChannelsNotice as (typeof ChannelsNoticeModule) extends null ? null : React.ComponentType import { SandboxManager } from 'src/utils/sandbox/sandbox-adapter.js' import { useShowGuestPassesUpsell, @@ -178,7 +181,7 @@ export function LogoV2(): React.ReactNode { - {ChannelsNoticeModule && } +{ChannelsNotice && } {isDebugMode() && ( Debug mode enabled @@ -202,9 +205,9 @@ export function LogoV2(): React.ReactNode { )} {announcement && ( - {!process.env.IS_DEMO && config.oauthAccount?.organizationName && ( + {!process.env.IS_DEMO && config.oauthAccount?.organizationName != null && ( - Message from {config.oauthAccount.organizationName}: + Message from {config.oauthAccount!.organizationName}: )} {announcement} @@ -297,7 +300,7 @@ export function LogoV2(): React.ReactNode { - {ChannelsNoticeModule && } +{ChannelsNotice && } {showSandboxStatus && ( @@ -313,7 +316,7 @@ export function LogoV2(): React.ReactNode { const welcomeMessage = formatWelcomeMessage(username) const modelLine = - !process.env.IS_DEMO && config.oauthAccount?.organizationName + !process.env.IS_DEMO && config.oauthAccount?.organizationName != null ? `${modelDisplayName} · ${billingType} · ${config.oauthAccount.organizationName}` : `${modelDisplayName} · ${billingType}` // Calculate cwd width accounting for agent name if present @@ -425,7 +428,7 @@ export function LogoV2(): React.ReactNode { - {ChannelsNoticeModule && } + {ChannelsNotice && } {isDebugMode() && ( Debug mode enabled @@ -449,7 +452,7 @@ export function LogoV2(): React.ReactNode { )} {announcement && ( - {!process.env.IS_DEMO && config.oauthAccount?.organizationName && ( + {!process.env.IS_DEMO && config.oauthAccount?.organizationName != null && ( Message from {config.oauthAccount.organizationName}: diff --git a/src/setup.ts b/src/setup.ts index f4977e555..1543fd3d6 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -390,7 +390,7 @@ export async function setup( try { await getRecentActivity() } catch (error) { - logError('Failed to load recent activity:', error) + logError(new Error('Failed to load recent activity', { cause: error })) } } diff --git a/src/utils/context.ts b/src/utils/context.ts index 5ec51871c..06f5f6202 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -46,7 +46,14 @@ export function modelSupports1M(model: string): boolean { return false } const canonical = getCanonicalName(model) - return canonical.includes('claude-sonnet-4') || canonical.includes('opus-4-6') + if (canonical.includes('claude-sonnet-4') || canonical.includes('opus-4-6')) { + return true + } + // Third-party 1M models: Alibaba Qwen, etc. + if (canonical.startsWith('qwen')) { + return true + } + return false } export function getContextWindowForModel( @@ -83,6 +90,12 @@ export function getContextWindowForModel( return cap.max_input_tokens } + // Independent 1M check for models like qwen that support 1M context but + // don't have getModelCapability data (e.g. OpenAI-compatible providers) + if (modelSupports1M(model)) { + return 1_000_000 + } + if (betas?.includes(CONTEXT_1M_BETA_HEADER) && modelSupports1M(model)) { return 1_000_000 }