From 4665f88921916bb697b555b5bdf655fa50623a96 Mon Sep 17 00:00:00 2001 From: chenchen Date: Thu, 7 May 2026 13:55:57 +0800 Subject: [PATCH] feat: route Heicode desktop login through code.xinghanlab.com Manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three concrete pieces: 1. Provider preset taijiaicloud now points at https://code.xinghanlab.com instead of the old api.taijiaicloud.com. Together with the stock resolveOAuthConfig fallback (/heicode/oauth/authorize), this flips oauthEnabled on for the login card and turns the existing browser-redirect bridge into the default flow. Card name + promo updated to reflect that this is "log in via Heicode Manager". 2. loginAndActivate softens its model probe. /v1/models is best-effort: only hard 401/403 auth failures abort login. 5xx / panics / empty lists fall back to preset.defaultModels so the user lands inside the app even if the gateway transiently misbehaves; they can re-pick models from Settings later. 3. heicode_oauth.go fallback page: /login → /sign-in (matches the actual SPA route), title/copy de-branded from "HeiCode/新 API 控制台" to plain "Heicode 控制台". Also picks up the prior unstaged Windows polish: WindowControls (min/ max/close + drag region) on the login screen, ProviderLoginCard + globals.css refinements that landed in earlier MSI builds. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src-tauri/capabilities/default.json | 2 + cc-haha/desktop/src-tauri/tauri.conf.json | 8 +- .../src/components/layout/TitleBar.tsx | 17 ++-- .../src/components/login/HeicodeLoginPage.tsx | 90 ++++++++++++------- .../components/login/ProviderLoginCard.tsx | 28 +++--- cc-haha/desktop/src/theme/globals.css | 20 ++++- cc-haha/src/server/api/heicode-auth.ts | 14 ++- .../src/server/config/providerPresets.json | 10 +-- heicode/controller/heicode_oauth.go | 10 +-- 9 files changed, 127 insertions(+), 72 deletions(-) diff --git a/cc-haha/desktop/src-tauri/capabilities/default.json b/cc-haha/desktop/src-tauri/capabilities/default.json index b532e82..6beae47 100644 --- a/cc-haha/desktop/src-tauri/capabilities/default.json +++ b/cc-haha/desktop/src-tauri/capabilities/default.json @@ -9,6 +9,8 @@ "core:window:allow-minimize", "core:window:allow-start-dragging", "core:window:allow-toggle-maximize", + "core:window:allow-is-maximized", + "core:event:default", "shell:allow-open", { "identifier": "shell:allow-execute", diff --git a/cc-haha/desktop/src-tauri/tauri.conf.json b/cc-haha/desktop/src-tauri/tauri.conf.json index c9330e6..6cf6421 100644 --- a/cc-haha/desktop/src-tauri/tauri.conf.json +++ b/cc-haha/desktop/src-tauri/tauri.conf.json @@ -12,14 +12,16 @@ "app": { "windows": [ { - "title": "HeiCode", + "title": "Heicode", "width": 1440, "height": 960, "minWidth": 960, "minHeight": 640, - "decorations": true, + "decorations": false, "transparent": false, - "acceptFirstMouse": true + "acceptFirstMouse": true, + "titleBarStyle": "Overlay", + "hiddenTitle": true } ], "security": { diff --git a/cc-haha/desktop/src/components/layout/TitleBar.tsx b/cc-haha/desktop/src/components/layout/TitleBar.tsx index 47ee7e7..3241297 100644 --- a/cc-haha/desktop/src/components/layout/TitleBar.tsx +++ b/cc-haha/desktop/src/components/layout/TitleBar.tsx @@ -1,5 +1,9 @@ import { useUIStore } from '../../stores/uiStore' import { useTranslation } from '../../i18n' +import { WindowControls } from './WindowControls' + +const isMacHost = typeof navigator !== 'undefined' + && /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || '') export function TitleBar() { const { activeView, setActiveView } = useUIStore() @@ -10,12 +14,12 @@ export function TitleBar() { className="h-[var(--titlebar-height)] flex items-center border-b border-[var(--color-border)] bg-[var(--color-surface)] select-none" data-tauri-drag-region > - {/* macOS traffic light spacer */} -
+ {/* macOS traffic-light spacer (native overlay buttons appear here) */} + {isMacHost ?
:
} {/* Logo */}
- Heicode + Heicode
{/* Navigation arrows */} @@ -57,11 +61,12 @@ export function TitleBar() {
- {/* Right: Settings */} -
- +
) diff --git a/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx b/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx index a0e73db..d38b1e1 100644 --- a/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx +++ b/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx @@ -2,10 +2,14 @@ // // 全屏登录页 — 当 Heicode 检测到尚未登录时由 AppShell 渲染。 // 仅 1 个登录入口:TaijiAICloud。 +// +// 顶部自绘 36px 高的 chrome(drag region + WindowControls),因为 +// tauri.conf.json 已设 decorations:false 以摆脱 Windows 系统灰边框。 import { useEffect } from 'react' import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore' import { ProviderLoginCard } from './ProviderLoginCard' +import { WindowControls } from '../layout/WindowControls' import { useTranslation } from '../../i18n' export function HeicodeLoginPage() { @@ -30,47 +34,69 @@ export function HeicodeLoginPage() { return (
-
-

- Heicode -

-

- {t('login.subtitle')} -

+ {/* Chrome strip — drag region + window controls */} +
+
+ + Heicode + +
+
- {!hasFetched && isLoading ? ( -
- {t('common.loading')} -
- ) : null} + {/* Subtle radial backdrop for premium feel */} +
- {hasFetched && providers.length === 0 ? ( -
- {t('login.errors.noProviders')} + {/* Content */} +
+
+

+ Heicode +

+

+ {t('login.subtitle')} +

- ) : null} - {hasFetched && providers.length > 0 ? ( -
- {providers.map((provider) => ( - - ))} -
- ) : null} + {!hasFetched && isLoading ? ( +
+ {t('common.loading')} +
+ ) : null} - {error ? ( -
- {error} -
- ) : null} + {hasFetched && providers.length === 0 ? ( +
+ {t('login.errors.noProviders')} +
+ ) : null} + {hasFetched && providers.length > 0 ? ( +
+ {providers.map((provider) => ( + + ))} +
+ ) : null} + + {error ? ( +
+ {error} +
+ ) : null} +
) } diff --git a/cc-haha/desktop/src/components/login/ProviderLoginCard.tsx b/cc-haha/desktop/src/components/login/ProviderLoginCard.tsx index 1ea4ce3..1a235e5 100644 --- a/cc-haha/desktop/src/components/login/ProviderLoginCard.tsx +++ b/cc-haha/desktop/src/components/login/ProviderLoginCard.tsx @@ -68,36 +68,34 @@ export function ProviderLoginCard({ provider }: Props) { const local = isLocalBaseUrl(provider.baseUrl) return ( -
-
-

+
+
+

{provider.name}

{provider.oauthEnabled ? ( - + {t('login.tags.recommended')} ) : ( - + {t('login.tags.comingSoon')} )}
- {/* Always-visible baseUrl indicator. Helps the user immediately tell - whether this card is pointing at the public TaijiAICloud endpoint - or has been overridden to a local heicode dev gateway. */} + {/* baseUrl indicator */}
- {t('login.baseUrl.label')} - + {t('login.baseUrl.label')} + {provider.baseUrl} {local ? ( @@ -107,7 +105,7 @@ export function ProviderLoginCard({ provider }: Props) {
{provider.promoText ? ( -

+

{provider.promoText}

) : null} @@ -117,7 +115,7 @@ export function ProviderLoginCard({ provider }: Props) { type="button" onClick={handleOAuth} disabled={!provider.oauthEnabled || isLoggingIn || busy !== null} - className="rounded-md bg-[image:var(--gradient-btn-primary)] px-4 py-2.5 text-sm text-[var(--color-btn-primary-fg)] shadow-[var(--shadow-button-primary)] transition-opacity hover:brightness-105 disabled:cursor-not-allowed disabled:opacity-50" + className="group/btn relative overflow-hidden rounded-[var(--radius-md)] border border-[var(--color-primary)]/40 bg-[var(--color-primary)] px-4 py-2.5 text-sm font-medium text-[var(--color-on-primary)] shadow-[var(--shadow-button-primary)] transition-all duration-200 hover:bg-[var(--color-primary-fixed-dim)] hover:shadow-[0_8px_24px_rgba(197,165,114,0.28)] disabled:cursor-not-allowed disabled:border-[var(--color-border)] disabled:bg-[var(--color-surface-container-high)] disabled:text-[var(--color-text-tertiary)] disabled:shadow-none" > {busy === 'oauth' ? t('login.oauth.opening') @@ -133,7 +131,7 @@ export function ProviderLoginCard({ provider }: Props) {
{localError ? ( -
+
{localError}
) : null} diff --git a/cc-haha/desktop/src/theme/globals.css b/cc-haha/desktop/src/theme/globals.css index c5fc08d..bc8a233 100644 --- a/cc-haha/desktop/src/theme/globals.css +++ b/cc-haha/desktop/src/theme/globals.css @@ -191,6 +191,7 @@ /* Border aliases */ --color-border: rgba(255, 255, 255, 0.08); + --color-border-hi: rgba(255, 255, 255, 0.14); --color-border-focus: var(--color-primary); --color-border-separator: rgba(255, 255, 255, 0.06); @@ -337,6 +338,7 @@ --sidebar-panel-bg-image: none; --color-border: rgba(0, 0, 0, 0.08); + --color-border-hi: rgba(0, 0, 0, 0.14); --color-border-focus: var(--color-primary); --color-border-separator: rgba(0, 0, 0, 0.06); @@ -637,11 +639,21 @@ button, input, textarea, select, a, [role="button"] { color: inherit; } -/* Scrollbar */ -::-webkit-scrollbar { width: 6px; height: 6px; } +/* Scrollbar — thin, subtle, premium */ +::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: transparent; } -::-webkit-scrollbar-thumb { background: var(--color-outline); border-radius: 9999px; opacity: 0.3; } -::-webkit-scrollbar-thumb:hover { opacity: 0.6; } +::-webkit-scrollbar-thumb { + background: color-mix(in srgb, var(--color-outline) 30%, transparent); + border-radius: 9999px; + border: 2px solid transparent; + background-clip: padding-box; + transition: background 160ms ease; +} +::-webkit-scrollbar-thumb:hover { + background: color-mix(in srgb, var(--color-outline) 60%, transparent); + background-clip: padding-box; +} +::-webkit-scrollbar-corner { background: transparent; } .sidebar-scroll-area { scrollbar-width: thin; diff --git a/cc-haha/src/server/api/heicode-auth.ts b/cc-haha/src/server/api/heicode-auth.ts index 83b19ec..cbe34f2 100644 --- a/cc-haha/src/server/api/heicode-auth.ts +++ b/cc-haha/src/server/api/heicode-auth.ts @@ -288,7 +288,14 @@ async function loginAndActivate( apiFormat: preset.apiFormat, }) - if (probe.models.length === 0) { + // Soft probe: if /v1/models is unreachable / panics / returns empty, fall back + // to the preset's defaultModels so the user can still complete login. They can + // re-pick models later from Settings once the gateway returns a proper list. + // Hard auth failures (401/403) still surface as model probe errors via fetchProviderModels; + // we treat any other failure (5xx, panic, network) as "skippable". + const isHardAuthError = + probe.error?.includes('HTTP 401') || probe.error?.includes('HTTP 403') + if (probe.models.length === 0 && isHardAuthError) { throw ApiError.badRequest( `校验失败: ${probe.error ?? '该 API Key 在 ' + preset.name + ' 上无可用模型'}`, ) @@ -299,7 +306,10 @@ async function loginAndActivate( const availableIds = new Set(probe.models.map(m => m.id)) const pick = (preferred: string): string => { if (preferred && availableIds.has(preferred)) return preferred - return probe.models[0]?.id ?? preferred + if (probe.models.length > 0) return probe.models[0].id + // No live model list — use the preset default verbatim. The user can change + // it later once the gateway is healthy. + return preferred } const models = { main: pick(preset.defaultModels.main), diff --git a/cc-haha/src/server/config/providerPresets.json b/cc-haha/src/server/config/providerPresets.json index 2f9145d..ce50e35 100644 --- a/cc-haha/src/server/config/providerPresets.json +++ b/cc-haha/src/server/config/providerPresets.json @@ -15,8 +15,8 @@ }, { "id": "taijiaicloud", - "name": "TaijiAICloud", - "baseUrl": "https://api.taijiaicloud.com", + "name": "Heicode", + "baseUrl": "https://code.xinghanlab.com", "apiFormat": "anthropic", "defaultModels": { "main": "claude-sonnet-4-6", @@ -25,9 +25,9 @@ "opus": "claude-opus-4-6" }, "needsApiKey": true, - "websiteUrl": "https://api.taijiaicloud.com", - "apiKeyUrl": "https://api.taijiaicloud.com/dashboard/keys", - "promoText": "TaijiAICloud 是 HeiCode 推荐的国内 LLM 网关,原生支持 Anthropic /v1/messages 协议,覆盖 GPT / Claude / Gemini 等主流模型。", + "websiteUrl": "https://code.xinghanlab.com", + "apiKeyUrl": "https://code.xinghanlab.com/dashboard/keys", + "promoText": "通过 Heicode Manager 登录,自动配置模型与额度。", "featured": true, "defaultEnv": { "API_TIMEOUT_MS": "3000000", diff --git a/heicode/controller/heicode_oauth.go b/heicode/controller/heicode_oauth.go index 94fac38..bc01ef7 100644 --- a/heicode/controller/heicode_oauth.go +++ b/heicode/controller/heicode_oauth.go @@ -152,14 +152,14 @@ func renderHeicodeLoginRequired(c *gin.Context, state, redirectURI, providerID s url.QueryEscape(redirectURI), url.QueryEscape(providerID), ) - loginURL := "/login" + loginURL := "/sign-in" body := fmt.Sprintf(` -HeiCode 登录授权 +Heicode 登录授权