Skip to content

Commit a7597cc

Browse files
authored
refine desktop pet task bubbles (#591)
1 parent 185a64d commit a7597cc

5 files changed

Lines changed: 239 additions & 33 deletions

File tree

src/apps/desktop/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ pub async fn run() {
388388
update_app_status,
389389
theme::show_agent_companion_desktop_pet,
390390
theme::hide_agent_companion_desktop_pet,
391+
theme::resize_agent_companion_desktop_pet,
391392
list_agent_companion_pets,
392393
import_agent_companion_pet_package,
393394
delete_agent_companion_pet_package,

src/apps/desktop/src/theme.rs

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use log::{debug, error, warn};
77
use tauri::{Manager, WebviewUrl};
88

99
const AGENT_COMPANION_WINDOW_LABEL: &str = "agent-companion-pet";
10-
const AGENT_COMPANION_WINDOW_WIDTH: f64 = 360.0;
11-
const AGENT_COMPANION_WINDOW_HEIGHT: f64 = 180.0;
10+
const AGENT_COMPANION_WINDOW_MIN_SIZE: f64 = 96.0;
11+
const AGENT_COMPANION_WINDOW_MAX_WIDTH: f64 = 360.0;
12+
const AGENT_COMPANION_WINDOW_MAX_HEIGHT: f64 = 240.0;
1213
const AGENT_COMPANION_WINDOW_MARGIN: i32 = 64;
1314

1415
#[derive(Debug, Clone)]
@@ -321,31 +322,104 @@ fn app_url(path: &str) -> WebviewUrl {
321322
}
322323
}
323324

324-
fn position_agent_companion_window(app: &tauri::AppHandle, window: &tauri::WebviewWindow) {
325+
fn agent_companion_default_position(
326+
app: &tauri::AppHandle,
327+
window: &tauri::WebviewWindow,
328+
) -> Option<tauri::LogicalPosition<f64>> {
325329
let monitor: Option<tauri::Monitor> = window
326330
.current_monitor()
327331
.ok()
328332
.flatten()
329333
.or_else(|| app.primary_monitor().ok().flatten());
330334

331-
let Some(monitor) = monitor else {
332-
return;
333-
};
335+
let monitor = monitor?;
334336

335337
let scale_factor = monitor.scale_factor();
336338
let area = monitor.work_area();
337339
let area_position = area.position.to_logical::<f64>(scale_factor);
338340
let area_size = area.size.to_logical::<f64>(scale_factor);
341+
let window_size = window
342+
.outer_size()
343+
.ok()
344+
.map(|size| size.to_logical::<f64>(scale_factor));
345+
let window_width = window_size
346+
.as_ref()
347+
.map(|size| size.width)
348+
.unwrap_or(AGENT_COMPANION_WINDOW_MIN_SIZE);
349+
let window_height = window_size
350+
.as_ref()
351+
.map(|size| size.height)
352+
.unwrap_or(AGENT_COMPANION_WINDOW_MIN_SIZE);
339353
let x = area_position.x + area_size.width
340-
- AGENT_COMPANION_WINDOW_WIDTH
354+
- window_width
341355
- f64::from(AGENT_COMPANION_WINDOW_MARGIN);
342356
let y = area_position.y + area_size.height
343-
- AGENT_COMPANION_WINDOW_HEIGHT
357+
- window_height
344358
- f64::from(AGENT_COMPANION_WINDOW_MARGIN);
345359

346-
if let Err(e) = window.set_position(tauri::LogicalPosition::new(
360+
Some(tauri::LogicalPosition::new(
347361
x.max(area_position.x),
348362
y.max(area_position.y),
363+
))
364+
}
365+
366+
fn position_agent_companion_window(app: &tauri::AppHandle, window: &tauri::WebviewWindow) {
367+
let Some(position) = agent_companion_default_position(app, window) else {
368+
return;
369+
};
370+
371+
if let Err(e) = window.set_position(position) {
372+
warn!("Failed to position Agent companion window: {}", e);
373+
}
374+
}
375+
376+
fn resize_agent_companion_window(
377+
app: &tauri::AppHandle,
378+
window: &tauri::WebviewWindow,
379+
width: f64,
380+
height: f64,
381+
) {
382+
let monitor: Option<tauri::Monitor> = window
383+
.current_monitor()
384+
.ok()
385+
.flatten()
386+
.or_else(|| app.primary_monitor().ok().flatten());
387+
let scale_factor = monitor.as_ref().map(|monitor| monitor.scale_factor());
388+
let old_size = scale_factor.and_then(|scale_factor| {
389+
window
390+
.outer_size()
391+
.ok()
392+
.map(|size| size.to_logical::<f64>(scale_factor))
393+
});
394+
let old_position = scale_factor.and_then(|scale_factor| {
395+
window
396+
.outer_position()
397+
.ok()
398+
.map(|position| position.to_logical::<f64>(scale_factor))
399+
});
400+
401+
if let Err(e) = window.set_size(tauri::LogicalSize::new(width, height)) {
402+
warn!("Failed to resize Agent companion window: {}", e);
403+
return;
404+
}
405+
406+
let next_position = old_position
407+
.zip(old_size)
408+
.map(|(position, size)| {
409+
tauri::LogicalPosition::new(
410+
position.x + size.width - width,
411+
position.y + size.height - height,
412+
)
413+
})
414+
.or_else(|| agent_companion_default_position(app, window));
415+
416+
let Some(position) = next_position else {
417+
return;
418+
};
419+
420+
if let Err(e) = window.set_position(tauri::LogicalPosition::new(
421+
position.x,
422+
position.y,
349423
)) {
350424
warn!("Failed to position Agent companion window: {}", e);
351425
}
@@ -366,17 +440,14 @@ pub async fn show_agent_companion_desktop_pet(app: tauri::AppHandle) -> Result<(
366440
let mut builder = tauri::WebviewWindowBuilder::new(&app, AGENT_COMPANION_WINDOW_LABEL, url)
367441
.title("BitFun Agent Companion")
368442
.inner_size(
369-
AGENT_COMPANION_WINDOW_WIDTH,
370-
AGENT_COMPANION_WINDOW_HEIGHT,
443+
AGENT_COMPANION_WINDOW_MIN_SIZE,
444+
AGENT_COMPANION_WINDOW_MIN_SIZE,
371445
)
372446
.max_inner_size(
373-
AGENT_COMPANION_WINDOW_WIDTH,
374-
AGENT_COMPANION_WINDOW_HEIGHT,
375-
)
376-
.min_inner_size(
377-
AGENT_COMPANION_WINDOW_WIDTH,
378-
AGENT_COMPANION_WINDOW_HEIGHT,
447+
AGENT_COMPANION_WINDOW_MAX_WIDTH,
448+
AGENT_COMPANION_WINDOW_MAX_HEIGHT,
379449
)
450+
.min_inner_size(1.0, 1.0)
380451
.resizable(false)
381452
.decorations(false)
382453
.transparent(true)
@@ -404,6 +475,18 @@ pub async fn show_agent_companion_desktop_pet(app: tauri::AppHandle) -> Result<(
404475
Ok(())
405476
}
406477

478+
#[tauri::command]
479+
pub async fn resize_agent_companion_desktop_pet(
480+
app: tauri::AppHandle,
481+
width: f64,
482+
height: f64,
483+
) -> Result<(), String> {
484+
if let Some(window) = app.get_webview_window(AGENT_COMPANION_WINDOW_LABEL) {
485+
resize_agent_companion_window(&app, &window, width, height);
486+
}
487+
Ok(())
488+
}
489+
407490
#[tauri::command]
408491
pub async fn hide_agent_companion_desktop_pet(app: tauri::AppHandle) -> Result<(), String> {
409492
if let Some(window) = app.get_webview_window(AGENT_COMPANION_WINDOW_LABEL) {

src/web-ui/src/app/App.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { aiExperienceConfigService } from '@/infrastructure/config/services/AIEx
1414
import { syncAgentCompanionDesktopWindow } from '@/infrastructure/config/services/AgentCompanionWindowService';
1515
import { buildAgentCompanionActivity, subscribeAgentCompanionActivity } from '@/flow_chat/utils/agentCompanionActivity';
1616
import { emitAgentCompanionActivity } from '@/flow_chat/services/AgentCompanionActivityBridge';
17+
import { FlowChatStore } from '@/flow_chat/store/FlowChatStore';
1718
import { useWorkspaceContext } from '../infrastructure/contexts/WorkspaceContext';
1819
import SplashScreen from './components/SplashScreen/SplashScreen';
1920
import { useGlobalSceneShortcuts } from './hooks/useGlobalSceneShortcuts';
@@ -185,6 +186,43 @@ function App() {
185186
void emitAgentCompanionActivity(activity);
186187
}), []);
187188

189+
useEffect(() => {
190+
let unlisten: (() => void) | null = null;
191+
void import('@tauri-apps/api/event')
192+
.then(({ listen }) => listen<{ sessionId?: string }>(
193+
'agent-companion://open-session',
194+
async event => {
195+
const sessionId = event.payload?.sessionId;
196+
if (!sessionId) return;
197+
198+
const flowChatStore = FlowChatStore.getInstance();
199+
if (flowChatStore.getState().sessions.has(sessionId)) {
200+
flowChatStore.switchSession(sessionId);
201+
}
202+
203+
try {
204+
const { invoke } = await import('@tauri-apps/api/core');
205+
await invoke('show_main_window');
206+
} catch (error) {
207+
log.warn('Failed to show main window from Agent companion bubble', {
208+
sessionId,
209+
error,
210+
});
211+
}
212+
},
213+
))
214+
.then(removeListener => {
215+
unlisten = removeListener;
216+
})
217+
.catch(error => {
218+
log.warn('Failed to listen for Agent companion session open events', error);
219+
});
220+
221+
return () => {
222+
unlisten?.();
223+
};
224+
}, []);
225+
188226
// Observe AI initialization state
189227
useEffect(() => {
190228
if (aiError) {

src/web-ui/src/app/components/AgentCompanionDesktopPet/AgentCompanionDesktopPet.scss

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,8 @@
1818
height: 100vh;
1919
position: relative;
2020
background: transparent;
21-
cursor: grab;
2221
user-select: none;
2322

24-
&:active {
25-
cursor: grabbing;
26-
}
27-
2823
&__pet {
2924
width: min(96px, 100vw);
3025
height: min(96px, 100vh);
@@ -52,18 +47,34 @@
5247

5348
&__bubbles {
5449
position: absolute;
55-
right: 88px;
56-
bottom: 24px;
57-
width: min(252px, calc(100vw - 104px));
58-
max-height: calc(100vh - 16px);
50+
right: calc(var(--bitfun-agent-companion-pet-size, 96px) - var(--bitfun-agent-companion-gap, 8px));
51+
bottom: 0;
52+
width: min(252px, calc(100vw - var(--bitfun-agent-companion-pet-size, 96px) - var(--bitfun-agent-companion-gap, 8px)));
53+
max-height: 100vh;
5954
display: flex;
6055
flex-direction: column;
6156
align-items: flex-end;
6257
gap: 6px;
63-
pointer-events: none;
58+
overflow-x: hidden;
59+
overflow-y: auto;
60+
overscroll-behavior: contain;
61+
pointer-events: auto;
62+
scrollbar-width: thin;
63+
64+
&::-webkit-scrollbar {
65+
width: 5px;
66+
}
67+
68+
&::-webkit-scrollbar-thumb {
69+
border-radius: 999px;
70+
background: rgba(15, 23, 42, 0.18);
71+
}
6472
}
6573

6674
&__bubble {
75+
appearance: none;
76+
text-align: left;
77+
font: inherit;
6778
max-width: 100%;
6879
min-width: 132px;
6980
padding: 7px 10px;
@@ -73,6 +84,17 @@
7384
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.14);
7485
color: #1f2937;
7586
backdrop-filter: blur(10px);
87+
cursor: pointer;
88+
89+
&:hover {
90+
transform: translateY(-1px);
91+
box-shadow: 0 12px 26px rgba(15, 23, 42, 0.18);
92+
}
93+
94+
&:focus-visible {
95+
outline: 2px solid rgba(59, 130, 246, 0.7);
96+
outline-offset: 2px;
97+
}
7698
}
7799

78100
&__bubble-title,

0 commit comments

Comments
 (0)