mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] desktop: dock gains the og toolbar actions (chat/browser/workflow/note/history); Applications no longer kills the app (getFileIcon native throw SIGTRAPped main; icons now decode via defaults+sips children)
This commit is contained in:
+30
-2
@@ -3097,8 +3097,36 @@ ipcMain.handle('get-app-icon', async (_event, name) => {
|
||||
if (!target) return null;
|
||||
if (appIconCache.has(name)) return appIconCache.get(name);
|
||||
try {
|
||||
const icon = await app.getFileIcon(target, { size: 'large' });
|
||||
const dataUrl = icon && !icon.isEmpty() ? icon.toDataURL() : null;
|
||||
let dataUrl = null;
|
||||
if (process.platform === 'darwin') {
|
||||
// NEVER app.getFileIcon here: a corrupt .icns raises a native ObjC exception no JS try/catch
|
||||
// can contain and SIGTRAPs the whole app (reproduced 2026-07-20: last IPC get-app-icon,
|
||||
// crashpad in_range_cast warning, death). sips does the decode in a disposable child instead.
|
||||
const { execFile } = require('child_process');
|
||||
const os = require('os');
|
||||
const run = (cmd, args) => new Promise((resolve, reject) => {
|
||||
execFile(cmd, args, { timeout: 5000 }, (err, stdout) => (err ? reject(err) : resolve(String(stdout).trim())));
|
||||
});
|
||||
const resources = path.join(target, 'Contents', 'Resources');
|
||||
let icnsName = await run('/usr/bin/defaults', ['read', path.join(target, 'Contents', 'Info'), 'CFBundleIconFile']).catch(() => '');
|
||||
if (icnsName && !icnsName.endsWith('.icns')) icnsName += '.icns';
|
||||
let icns = icnsName ? path.join(resources, icnsName) : '';
|
||||
if (!icns || !fs.existsSync(icns)) {
|
||||
const alt = fs.existsSync(resources) ? fs.readdirSync(resources).find((f) => f.endsWith('.icns')) : null;
|
||||
icns = alt ? path.join(resources, alt) : '';
|
||||
}
|
||||
if (icns && fs.existsSync(icns)) {
|
||||
const outPng = path.join(os.tmpdir(), `osw-icon-${process.pid}-${Date.now()}.png`);
|
||||
await run('/usr/bin/sips', ['-s', 'format', 'png', '-z', '128', '128', icns, '--out', outPng]).catch(() => '');
|
||||
if (fs.existsSync(outPng)) {
|
||||
dataUrl = `data:image/png;base64,${fs.readFileSync(outPng).toString('base64')}`;
|
||||
fs.rmSync(outPng, { force: true });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const icon = await app.getFileIcon(target, { size: 'large' });
|
||||
dataUrl = icon && !icon.isEmpty() ? icon.toDataURL() : null;
|
||||
}
|
||||
appIconCache.set(name, dataUrl);
|
||||
return dataUrl;
|
||||
} catch (_) {
|
||||
|
||||
@@ -339,8 +339,14 @@ const DashboardToolbar = React.forwardRef<HTMLDivElement, Props>(
|
||||
onAddBrowser();
|
||||
}
|
||||
};
|
||||
// The desktop dock's History tile opens the same popover as Cmd+O.
|
||||
const handleOpenHistoryEvent = () => handleOpenHistory();
|
||||
window.addEventListener('keydown', handleKey);
|
||||
return () => window.removeEventListener('keydown', handleKey);
|
||||
window.addEventListener('openswarm:open-history', handleOpenHistoryEvent);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKey);
|
||||
window.removeEventListener('openswarm:open-history', handleOpenHistoryEvent);
|
||||
};
|
||||
}, [handleOpenViewPicker, handleOpenHistory, onAddBrowser]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -263,6 +263,9 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
onHighlightCard?.(cardId);
|
||||
}}
|
||||
onApplications={() => setAppsWindowOpen((v) => !v)}
|
||||
onNewAgent={onNewAgent}
|
||||
onAddBrowser={onAddBrowser}
|
||||
onAddNote={onAddNote}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import LanguageIcon from '@mui/icons-material/Language';
|
||||
import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
|
||||
import EventRepeatIcon from '@mui/icons-material/EventRepeat';
|
||||
import StickyNote2OutlinedIcon from '@mui/icons-material/StickyNote2Outlined';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import DashboardGlyph from '../canvas/DashboardGlyph';
|
||||
import { openWorkflowsApp } from '@/shared/state/dashboardLayoutSlice';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import AppsRoundedIcon from '@mui/icons-material/AppsRounded';
|
||||
import EditNoteIcon from '@mui/icons-material/EditNote';
|
||||
@@ -51,6 +57,9 @@ interface DesktopDockProps {
|
||||
selectedIds: string[];
|
||||
onFocusCard: (id: string, rect: CardRect) => void;
|
||||
onApplications: () => void;
|
||||
onNewAgent: () => void;
|
||||
onAddBrowser: () => void;
|
||||
onAddNote: () => void;
|
||||
}
|
||||
|
||||
const TILE = 30;
|
||||
@@ -83,6 +92,9 @@ function DesktopDock({
|
||||
selectedIds,
|
||||
onFocusCard,
|
||||
onApplications,
|
||||
onNewAgent,
|
||||
onAddBrowser,
|
||||
onAddNote,
|
||||
}: DesktopDockProps): React.ReactElement | null {
|
||||
const dispatch = useAppDispatch();
|
||||
const [hovered, setHovered] = useState<{ id: string; top: number } | null>(null);
|
||||
@@ -250,6 +262,37 @@ function DesktopDock({
|
||||
{entries.length > 0 && (
|
||||
<Box sx={{ width: TILE - 8, height: '1px', background: 'rgba(255,255,255,0.14)' }} />
|
||||
)}
|
||||
{/* The og toolbar's actions, dock-resident: chat, browser, workflow, note, history. */}
|
||||
{([
|
||||
{ label: 'New chat', icon: <ChatBubbleOutlineIcon sx={{ fontSize: 16, color: '#e8e8ee' }} />, act: onNewAgent },
|
||||
{ label: 'New browser', icon: <LanguageIcon sx={{ fontSize: 17, color: '#e8e8ee' }} />, act: onAddBrowser },
|
||||
{ label: 'Workflows', icon: <EventRepeatIcon sx={{ fontSize: 16, color: '#e8e8ee' }} />, act: () => dispatch(openWorkflowsApp()) },
|
||||
{ label: 'New note', icon: <StickyNote2OutlinedIcon sx={{ fontSize: 16, color: '#e8e8ee' }} />, act: onAddNote },
|
||||
{ label: 'History', icon: <HistoryIcon sx={{ fontSize: 17, color: '#e8e8ee' }} />, act: () => window.dispatchEvent(new CustomEvent('openswarm:open-history')) },
|
||||
] as const).map((a) => (
|
||||
<Tooltip key={a.label} title={a.label} placement="right">
|
||||
<Box
|
||||
onClick={a.act}
|
||||
onMouseEnter={endHover}
|
||||
sx={{
|
||||
width: TILE,
|
||||
height: TILE,
|
||||
borderRadius: '9px',
|
||||
background: 'linear-gradient(135deg, #5a5a62, #34343c)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
flexShrink: 0,
|
||||
transition: 'transform 0.15s ease',
|
||||
'&:hover': { transform: 'scale(1.12)' },
|
||||
}}
|
||||
>
|
||||
{a.icon}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
))}
|
||||
<Box sx={{ width: TILE - 8, height: '1px', background: 'rgba(255,255,255,0.14)' }} />
|
||||
<Box
|
||||
onClick={() => dispatch(openSettingsModal(undefined))}
|
||||
onMouseEnter={endHover}
|
||||
|
||||
Reference in New Issue
Block a user