diff --git a/apps/mini/README.md b/apps/mini/README.md index 89af3d9..e6c57f8 100644 --- a/apps/mini/README.md +++ b/apps/mini/README.md @@ -9,5 +9,5 @@ npm run desktop:dev ``` - Web 开发端口:`5180` -- 桌面窗口默认宽度:`420`;启动后高度自动适配当前屏幕全高,并停靠在屏幕右侧 +- 桌面窗口默认宽度:`420`;启动后高度自动适配当前屏幕可用工作区,不遮挡 Windows 任务栏,并停靠在屏幕右侧 - 左右停靠状态保存在本地;在 Tauri 桌面环境中会移动实际窗口。 diff --git a/apps/mini/scripts/visual-check.mjs b/apps/mini/scripts/visual-check.mjs index da345f7..a22e7ed 100644 --- a/apps/mini/scripts/visual-check.mjs +++ b/apps/mini/scripts/visual-check.mjs @@ -58,9 +58,8 @@ await page.route('http://localhost:9150/api/v1/**', async (route) => { await page.goto(`http://127.0.0.1:${port}/`, { waitUntil: 'networkidle' }) await page.screenshot({ path: 'test-results/mini-login.png', fullPage: true }) -await page.locator('.login-note input').nth(1).fill('demo@senlin.ai') -await page.locator('.login-note input').nth(2).fill('password123') -await page.getByRole('button', { name: '连接工作台' }).click() +const loginDefaults = await page.locator('.login-note input').evaluateAll((inputs) => inputs.map((input) => input.value)) +await page.locator('.login-note input').nth(2).press('Enter') await page.waitForSelector('.mini-shell') await page.waitForTimeout(400) await page.screenshot({ path: 'test-results/mini-home-right.png', fullPage: true }) @@ -97,6 +96,7 @@ await browser.close() await server.close() const failures = [] +if (loginDefaults[1] !== 'demo@senlin.ai' || loginDefaults[2] !== 'password123') failures.push(`login defaults failed: ${JSON.stringify(loginDefaults)}`) if (errors.length) failures.push(`console errors: ${errors.join('; ')}`) if (home.width !== 420) failures.push(`expected 420px shell, got ${home.width}`) if (home.navCount !== 5) failures.push(`expected five top navigation items, got ${home.navCount}`) diff --git a/apps/mini/src-tauri/src/lib.rs b/apps/mini/src-tauri/src/lib.rs index 26cc11a..533d6bc 100644 --- a/apps/mini/src-tauri/src/lib.rs +++ b/apps/mini/src-tauri/src/lib.rs @@ -7,20 +7,19 @@ fn dock_window(window: Window, side: String) -> Result<(), String> { .map_err(|error| error.to_string())? .ok_or_else(|| "无法识别当前屏幕".to_string())?; let window_size = window.outer_size().map_err(|error| error.to_string())?; - let monitor_position = monitor.position(); - let monitor_size = monitor.size(); - let dock_size = PhysicalSize::new(window_size.width, monitor_size.height); + let work_area = monitor.work_area(); + let dock_size = PhysicalSize::new(window_size.width, work_area.size.height); let x = if side == "left" { - monitor_position.x + work_area.position.x } else { - monitor_position.x + monitor_size.width as i32 - dock_size.width as i32 + work_area.position.x + work_area.size.width as i32 - dock_size.width as i32 }; window .set_size(dock_size) .map_err(|error| error.to_string())?; window - .set_position(PhysicalPosition::new(x, monitor_position.y)) + .set_position(PhysicalPosition::new(x, work_area.position.y)) .map_err(|error| error.to_string())?; window .set_always_on_top(true) diff --git a/apps/mini/src/App.tsx b/apps/mini/src/App.tsx index 917e284..70db6cd 100644 --- a/apps/mini/src/App.tsx +++ b/apps/mini/src/App.tsx @@ -307,13 +307,27 @@ function MoreView({ workspace, openAction }: ViewProps) { function Login({ onLogin }: { onLogin: (session: ApiSession) => void }) { const [server, setServer] = useState('http://localhost:9150') - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') + const [email, setEmail] = useState('demo@senlin.ai') + const [password, setPassword] = useState('password123') const [loading, setLoading] = useState(false) const [error, setError] = useState('') + + async function submitLogin() { + if (loading) return + setLoading(true) + setError('') + try { + onLogin(await login(server, email.trim(), password)) + } catch (requestError) { + setError(errorMessage(requestError)) + } finally { + setLoading(false) + } + } + return (
-
+
{ event.preventDefault(); void submitLogin() }}> 森林AI Mini 停靠在屏幕一侧,随时记录与推进项目 @@ -321,9 +335,9 @@ function Login({ onLogin }: { onLogin: (session: ApiSession) => void }) { {error ? : null} - + v1.0 完整版 -
+
) } diff --git a/apps/mini/src/api.ts b/apps/mini/src/api.ts index 0a9f471..5476512 100644 --- a/apps/mini/src/api.ts +++ b/apps/mini/src/api.ts @@ -192,7 +192,9 @@ function useSessionBase(session: ApiSession) { } function normalizeBaseUrl(value: string) { - return value.trim().replace(/\/+$/, '') + const normalized = value.trim().replace(/\/+$/, '') + if (!normalized || /^https?:\/\//i.test(normalized)) return normalized + return `http://${normalized}` } function slugify(value: string) { diff --git a/backend/etc/agent.dev.yaml b/backend/etc/agent.dev.yaml index bb89521..e665278 100644 --- a/backend/etc/agent.dev.yaml +++ b/backend/etc/agent.dev.yaml @@ -9,6 +9,8 @@ ai_key_encryption_secret: "development-ai-key-secret-change-me" allowed_origins: - "http://localhost:5173" - "http://127.0.0.1:5173" + - "http://localhost:5180" + - "http://127.0.0.1:5180" - "http://localhost:4173" - "http://127.0.0.1:4173" - "http://localhost:4174" diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go index e7e8cd0..7a4a9cd 100644 --- a/backend/internal/config/config_test.go +++ b/backend/internal/config/config_test.go @@ -23,6 +23,8 @@ func TestDevelopmentConfigMatchesLocalWorkspaceContract(t *testing.T) { require.ElementsMatch(t, []string{ "http://localhost:5173", "http://127.0.0.1:5173", + "http://localhost:5180", + "http://127.0.0.1:5180", "http://localhost:4173", "http://127.0.0.1:4173", "http://localhost:4174", diff --git a/backend/internal/httpx/router.go b/backend/internal/httpx/router.go index b2fe6ae..ac43a47 100644 --- a/backend/internal/httpx/router.go +++ b/backend/internal/httpx/router.go @@ -56,6 +56,8 @@ func cors(cfg config.Config) gin.HandlerFunc { origins = []string{ "http://localhost:5173", "http://127.0.0.1:5173", + "http://localhost:5180", + "http://127.0.0.1:5180", "http://tauri.localhost", } } diff --git a/backend/internal/httpx/router_test.go b/backend/internal/httpx/router_test.go index 2f01012..c3c35f9 100644 --- a/backend/internal/httpx/router_test.go +++ b/backend/internal/httpx/router_test.go @@ -81,6 +81,18 @@ func TestRouterAddsCORSHeadersForLocalWebClient(t *testing.T) { require.Contains(t, rec.Header().Get("Access-Control-Allow-Headers"), "Authorization") } +func TestRouterAddsCORSHeadersForMiniClient(t *testing.T) { + router := NewRouter(config.Config{Env: "test"}, testRegistrar{}) + req := httptest.NewRequest(http.MethodGet, "/api/v1/ping", nil) + req.Header.Set("Origin", "http://localhost:5180") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "http://localhost:5180", rec.Header().Get("Access-Control-Allow-Origin")) +} + func TestAPIV1LoginRemainsPublic(t *testing.T) { router := NewProtectedRouter(config.Config{Env: "test"}, func(token string) (uint, error) { return 0, http.ErrNoCookie