diff --git a/e2e/demo/login-dialog.demo.ts b/e2e/demo/login-dialog.demo.ts index f52a83c..d8bd329 100644 --- a/e2e/demo/login-dialog.demo.ts +++ b/e2e/demo/login-dialog.demo.ts @@ -32,8 +32,9 @@ test.describe('Login Dialog Demo', () => { // Verify dialog title await expect(webviewPage.getByText('SSO 认证', { exact: true })).toBeVisible(); - // Verify server URL is displayed - await expect(webviewPage.getByText('https://wave-ai.example.com')).toBeVisible(); + // Verify server URL input is pre-filled + const serverUrlInput = webviewPage.locator('#login-serverUrl'); + await expect(serverUrlInput).toHaveValue('https://wave-ai.example.com'); // Verify login button is visible await expect(webviewPage.getByText('SSO 登录', { exact: true })).toBeVisible(); @@ -77,7 +78,7 @@ test.describe('Login Dialog Demo', () => { await dialog.screenshot({ path: 'docs/public/screenshots/spec-login-dialog-authenticated.png' }); }); - test('should show warning when no server URL configured', async ({ webviewPage }) => { + test('should show editable serverUrl input when no server URL configured', async ({ webviewPage }) => { await webviewPage.evaluate(() => { (window as any).simulateExtensionMessage({ command: 'showDialog', @@ -102,9 +103,13 @@ test.describe('Login Dialog Demo', () => { }); await expect(webviewPage.getByText('SSO 认证', { exact: true })).toBeVisible(); - await expect(webviewPage.getByText('请先在 /config 中配置服务端链接')).toBeVisible(); - // Login button should be disabled + // ServerUrl input should be visible and empty + const serverUrlInput = webviewPage.locator('#login-serverUrl'); + await expect(serverUrlInput).toBeVisible(); + await expect(serverUrlInput).toHaveValue(''); + + // Login button should be disabled when serverUrl is empty const loginBtn = webviewPage.getByText('SSO 登录', { exact: true }); await expect(loginBtn).toBeDisabled(); diff --git a/e2e/webview/model-status-login-commands.test.ts b/e2e/webview/model-status-login-commands.test.ts index 9df2c33..88654c9 100644 --- a/e2e/webview/model-status-login-commands.test.ts +++ b/e2e/webview/model-status-login-commands.test.ts @@ -100,8 +100,9 @@ test.describe('Model/Status/Login Slash Commands', () => { // Verify login dialog is visible await expect(webviewPage.getByText('SSO 认证', { exact: true })).toBeVisible(); - // Verify server URL is displayed - await expect(webviewPage.getByText('https://wave.example.com')).toBeVisible(); + // Verify server URL input is pre-filled + const serverUrlInput = webviewPage.locator('#login-serverUrl'); + await expect(serverUrlInput).toHaveValue('https://wave.example.com'); // Verify getAuthStatus message was sent to extension const messages = await webviewPage.evaluate(() => (window as any).getTestMessages()); @@ -221,4 +222,55 @@ test.describe('Model/Status/Login Slash Commands', () => { await expect(webviewPage.getByText('test@example.com')).toBeVisible(); await expect(webviewPage.getByText('登出', { exact: true })).toBeVisible(); }); + + test('should save serverUrl from login dialog via updateConfiguration', async ({ webviewPage }) => { + const input = webviewPage.getByTestId('message-input'); + await input.focus(); + + // Simulate extension sending configuration data with empty serverUrl + await webviewPage.evaluate(() => { + (window as any).simulateExtensionMessage({ + command: 'configurationResponse', + configurationData: { + serverUrl: '' + } + }); + }); + + // Open login dialog + await input.type('/login'); + await webviewPage.keyboard.press('Enter'); + + // Verify login dialog is visible + await expect(webviewPage.getByText('SSO 认证', { exact: true })).toBeVisible(); + + // Type serverUrl into the input + const serverUrlInput = webviewPage.locator('#login-serverUrl'); + await serverUrlInput.fill('https://wave.example.com'); + + // Click save button for serverUrl + await webviewPage.locator('#login-save-serverUrl').click(); + + // Verify updateConfiguration message was sent with serverUrl + const messages = await webviewPage.evaluate(() => (window as any).getTestMessages()); + const updateConfigMsg = messages.find((m: any) => m.command === 'updateConfiguration'); + expect(updateConfigMsg).toBeDefined(); + expect(updateConfigMsg.configurationData.serverUrl).toBe('https://wave.example.com'); + }); + + test('should not have serverUrl field in config dialog', async ({ webviewPage }) => { + const input = webviewPage.getByTestId('message-input'); + await input.focus(); + + // Open config dialog + await input.type('/config'); + await webviewPage.keyboard.press('Enter'); + + // Verify config dialog is visible + await expect(webviewPage.getByText('配置设置', { exact: true })).toBeVisible(); + + // Verify serverUrl input is NOT present in config dialog + const serverUrlInput = webviewPage.locator('#serverUrl'); + await expect(serverUrlInput).not.toBeVisible(); + }); }); diff --git a/webview/src/components/ChatApp.tsx b/webview/src/components/ChatApp.tsx index 168684a..c2023a0 100644 --- a/webview/src/components/ChatApp.tsx +++ b/webview/src/components/ChatApp.tsx @@ -316,7 +316,10 @@ export const ChatApp: React.FC = ({ vscode }) => { dispatch({ type: 'SHOW_DIALOG', payload: { type: message.dialogType } }); break; case 'configurationUpdated': - dispatch({ type: 'HIDE_DIALOG' }); + // Only close config dialog; keep login dialog open for continued SSO flow + if (stateRef.current.activeDialog !== 'login') { + dispatch({ type: 'HIDE_DIALOG' }); + } break; case 'statusResponse': if (message.configurationData) { diff --git a/webview/src/components/ConfigDialog.tsx b/webview/src/components/ConfigDialog.tsx index aab3989..0084085 100644 --- a/webview/src/components/ConfigDialog.tsx +++ b/webview/src/components/ConfigDialog.tsx @@ -82,18 +82,6 @@ const ConfigDialog: React.FC = ({
-
- - handleInputChange('serverUrl', e.target.value)} - placeholder={configurationData?.envServerUrl || 'WAVE_SERVER_URL'} - disabled={isLoading} - /> -
-
= ({ const [authUser, setAuthUser] = useState<{ id: string; email?: string } | null>(null); const [authLoading, setAuthLoading] = useState(false); const [authMessage, setAuthMessage] = useState(''); + const [serverUrlInput, setServerUrlInput] = useState(''); + const [serverUrlSaved, setServerUrlSaved] = useState(false); const dialogRef = useRef(null); + // Initialize serverUrl input from configuration + useEffect(() => { + setServerUrlInput(configurationData?.serverUrl || ''); + setServerUrlSaved(!!configurationData?.serverUrl); + }, [configurationData?.serverUrl]); + // Fetch auth status on mount useEffect(() => { vscode?.postMessage({ command: 'getAuthStatus' }); @@ -66,6 +74,17 @@ const LoginDialog: React.FC = ({ vscode?.postMessage({ command: 'logout' }); }; + const handleSaveServerUrl = () => { + vscode?.postMessage({ + command: 'updateConfiguration', + configurationData: { + ...configurationData, + serverUrl: serverUrlInput + } + }); + setServerUrlSaved(true); + }; + // Click outside / Escape to close useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -86,7 +105,7 @@ const LoginDialog: React.FC = ({ }; }, [onClose]); - const serverUrl = configurationData?.serverUrl || configurationData?.envServerUrl; + const serverUrl = serverUrlSaved ? serverUrlInput : (configurationData?.serverUrl || configurationData?.envServerUrl || ''); return (
@@ -98,8 +117,25 @@ const LoginDialog: React.FC = ({
- -
{serverUrl || '未配置'}
+ +
+ { setServerUrlInput(e.target.value); setServerUrlSaved(false); }} + placeholder={configurationData?.envServerUrl || 'WAVE_SERVER_URL'} + /> + +
@@ -137,9 +173,6 @@ const LoginDialog: React.FC = ({ {authMessage}
)} - {!serverUrl && !isAuthenticated && ( -
请先在 /config 中配置服务端链接
- )}
diff --git a/webview/src/styles/ConfigurationDialog.css b/webview/src/styles/ConfigurationDialog.css index 829c507..507bacf 100644 --- a/webview/src/styles/ConfigurationDialog.css +++ b/webview/src/styles/ConfigurationDialog.css @@ -652,6 +652,39 @@ input:checked + .slider:before { color: var(--vscode-errorForeground); } +/* Login Dialog - Server URL row */ +.login-serverUrl-row { + display: flex; + gap: 6px; + align-items: center; +} + +.login-serverUrl-row input { + flex: 1; + min-width: 0; +} + +.login-save-serverUrl-btn { + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + padding: 6px 10px; + border-radius: 3px; + cursor: pointer; + font-size: 12px; + white-space: nowrap; + flex-shrink: 0; +} + +.login-save-serverUrl-btn:hover { + background-color: var(--vscode-button-hoverBackground); +} + +.login-save-serverUrl-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + /* MCP Server Management */ .mcp-container { display: flex;