diff --git a/apps/web/src/app/App.svelte b/apps/web/src/app/App.svelte
index a4c6389..a5d8dce 100644
--- a/apps/web/src/app/App.svelte
+++ b/apps/web/src/app/App.svelte
@@ -1,76 +1,21 @@
-
-
-
-
-
-
- AI 整理建议
-
- 已选择 {selectedSuggestionCount} 项
-
-
-
+{#if currentUser}
+
+ SenlinAI Workbench
+ Signed in as {currentUser.account}
+
+{:else}
+
+{/if}
diff --git a/apps/web/src/features/auth/ServerLogin.svelte b/apps/web/src/features/auth/ServerLogin.svelte
index 8e80261..8e16cb9 100644
--- a/apps/web/src/features/auth/ServerLogin.svelte
+++ b/apps/web/src/features/auth/ServerLogin.svelte
@@ -1,16 +1,11 @@
-
+
+
+ SA
+
+
Private workbench
+
SenlinAI Workbench
+
+
+
+
+
diff --git a/apps/web/src/features/auth/ServerLogin.test.ts b/apps/web/src/features/auth/ServerLogin.test.ts
new file mode 100644
index 0000000..7298b4b
--- /dev/null
+++ b/apps/web/src/features/auth/ServerLogin.test.ts
@@ -0,0 +1,49 @@
+import '@testing-library/jest-dom/vitest';
+import { fireEvent, render, screen } from '@testing-library/svelte';
+import { describe, expect, it, vi } from 'vitest';
+import ServerLogin from './ServerLogin.svelte';
+
+describe('ServerLogin', () => {
+ it('renders server, account, and password fields', () => {
+ render(ServerLogin, { props: { apiBase: 'http://localhost:8080' } });
+
+ expect(screen.getByLabelText('Server IP or domain')).toBeInTheDocument();
+ expect(screen.getByLabelText('Email or username')).toBeInTheDocument();
+ expect(screen.getByLabelText('Password')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument();
+ });
+
+ it('normalizes server address and emits login details', async () => {
+ const onLogin = vi.fn();
+ render(ServerLogin, {
+ props: {
+ apiBase: 'localhost:8080',
+ onLogin,
+ },
+ });
+
+ await fireEvent.input(screen.getByLabelText('Server IP or domain'), {
+ target: { value: '10.0.0.12:8080' },
+ });
+ await fireEvent.input(screen.getByLabelText('Email or username'), {
+ target: { value: 'david@example.com' },
+ });
+ await fireEvent.input(screen.getByLabelText('Password'), {
+ target: { value: 'secret' },
+ });
+ await fireEvent.click(screen.getByRole('button', { name: 'Log in' }));
+
+ expect(onLogin).toHaveBeenCalledWith({
+ apiBase: 'http://10.0.0.12:8080',
+ account: 'david@example.com',
+ });
+ });
+
+ it('shows an error when account or password is missing', async () => {
+ render(ServerLogin, { props: { apiBase: 'http://localhost:8080' } });
+
+ await fireEvent.click(screen.getByRole('button', { name: 'Log in' }));
+
+ expect(screen.getByText('Enter an account and password.')).toBeInTheDocument();
+ });
+});