From 3e91c1539d6dca5af688aec0a4d654ff9312fb99 Mon Sep 17 00:00:00 2001 From: Wu-kung <1434246346@qq.com> Date: Wed, 15 Oct 2025 12:24:36 +0800 Subject: [PATCH 1/2] fix: enforce model selection(#313) --- src/ui/OnBoarding.tsx | 81 ------------------------------------------- src/ui/store.ts | 19 ++++++++-- 2 files changed, 17 insertions(+), 83 deletions(-) delete mode 100644 src/ui/OnBoarding.tsx diff --git a/src/ui/OnBoarding.tsx b/src/ui/OnBoarding.tsx deleted file mode 100644 index 8d344fd3..00000000 --- a/src/ui/OnBoarding.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Box, Text, useInput } from 'ink'; -import React, { useEffect, useState } from 'react'; -import { ModelSelect } from '../slash-commands/builtin/model'; -import { useAppStore } from './store'; - -export function OnBoarding() { - const { providers } = useAppStore(); - const [showModelSelect, setShowModelSelect] = useState(false); - const [completed, setCompleted] = useState(false); - - useInput((input, key) => { - if (key.return && !showModelSelect && !completed) { - setShowModelSelect(true); - } - if (key.escape) { - process.exit(0); - } - }); - - if (completed) { - return ( - - ✓ Model configured successfully - - ); - } - - if (showModelSelect) { - return ( - { - setShowModelSelect(false); - }} - onSelect={() => { - setCompleted(true); - }} - /> - ); - } - - return ( - - - ⚠ Model Configuration Required - - - - You have not configured a model yet. Please set an API key in your - environment for one of the providers we support: - - - - {Object.entries(providers).map(([id, provider]) => { - if (!provider.env.length) { - return null; - } - return ( - - • {provider.name} - - - {provider.env[0]} - - ); - })} - - - - Press{' '} - - Enter - {' '} - to select a model after setting your API key. Or press{' '} - - Esc - {' '} - to exit. - - - - ); -} diff --git a/src/ui/store.ts b/src/ui/store.ts index 95fa6a0a..42ae937d 100644 --- a/src/ui/store.ts +++ b/src/ui/store.ts @@ -346,8 +346,15 @@ export const useAppStore = create()( }, send: async (message) => { - const { bridge, cwd, sessionId, planMode, status, pastedTextMap } = - get(); + const { + bridge, + cwd, + sessionId, + planMode, + status, + pastedTextMap, + model, + } = get(); bridge.request('utils.telemetry', { cwd, @@ -355,6 +362,14 @@ export const useAppStore = create()( payload: { message, sessionId }, }); + if (!isSlashCommand(message) && !model) { + set({ + status: 'failed', + error: 'Please select a model first (use /model command)', + }); + return; + } + // Check if processing, queue the message if (isExecuting(status)) { get().addToQueue(message); From 02d368e86f0df8d39c85e48ee92074b8615e8dd4 Mon Sep 17 00:00:00 2001 From: Wu-kung <1434246346@qq.com> Date: Wed, 15 Oct 2025 18:49:22 +0800 Subject: [PATCH 2/2] feat: add GitHub Copilot login check before model selection --- src/model.ts | 5 --- src/nodeBridge.ts | 22 ++++++++++++ src/slash-commands/builtin/model.tsx | 54 +++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/model.ts b/src/model.ts index b1f2f6c8..c55d8ab2 100644 --- a/src/model.ts +++ b/src/model.ts @@ -810,11 +810,6 @@ export const providers: ProvidersMap = { const githubDataPath = path.join(globalConfigDir, 'githubCopilot.json'); const githubProvider = new GithubProvider({ authFile: githubDataPath }); const token = await githubProvider.access(); - if (!token) { - throw new Error( - 'Failed to get GitHub Copilot token, use /login to login first', - ); - } return createOpenAI({ baseURL: 'https://api.individual.githubcopilot.com', headers: { diff --git a/src/nodeBridge.ts b/src/nodeBridge.ts index 0f4c3c7b..1ae138a2 100644 --- a/src/nodeBridge.ts +++ b/src/nodeBridge.ts @@ -1,3 +1,4 @@ +import path from 'path'; import { compact } from './compact'; import { type ApprovalMode, type Config, ConfigManager } from './config'; import { CANCELED_MESSAGE_TEXT } from './constants'; @@ -932,6 +933,27 @@ class NodeHandlerRegistry { }; }, ); + + this.messageBus.registerHandler( + 'utils.checkGithubCopilotLogin', + async (data: { cwd: string }) => { + const { cwd } = data; + const context = await this.getContext(cwd); + const { GithubProvider } = await import('./providers/githubCopilot'); + const githubDataPath = path.join( + context.paths.globalConfigDir, + 'githubCopilot.json', + ); + const githubProvider = new GithubProvider({ authFile: githubDataPath }); + const token = await githubProvider.access(); + return { + success: true, + data: { + loggedIn: !!token, + }, + }; + }, + ); } } diff --git a/src/slash-commands/builtin/model.tsx b/src/slash-commands/builtin/model.tsx index c67ebc69..98b05b28 100644 --- a/src/slash-commands/builtin/model.tsx +++ b/src/slash-commands/builtin/model.tsx @@ -29,6 +29,7 @@ export const ModelSelect: React.FC = ({ modelId: string; } | null>(null); const [groupedModels, setGroupedModels] = useState([]); + const [error, setError] = useState(null); useEffect(() => { bridge.request('models.list', { cwd }).then((result) => { @@ -40,6 +41,54 @@ export const ModelSelect: React.FC = ({ }); }, [cwd]); + const handleSelect = async (item: { value: string }) => { + if (item.value.startsWith('github-copilot/')) { + const loginCheckResult = await bridge.request( + 'utils.checkGithubCopilotLogin', + { cwd }, + ); + if (!loginCheckResult.data.loggedIn) { + setError( + 'GitHub Copilot is not logged in. Please use /login command first.', + ); + return; + } + } + setError(null); + setModel(item.value); + onSelect(item.value); + }; + + useInput((_input, key) => { + if (key.return && error) { + setError(null); + } + }); + + if (error) { + return ( + + + + Login Required + + + + {error} + + + Press Enter to continue... + + + ); + } + return ( = ({ itemsPerPage={15} enableSearch={true} onCancel={() => onExit(currentModel)} - onSelect={(item) => { - setModel(item.value); - onSelect(item.value); - }} + onSelect={handleSelect} />