Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions camelid-desktop/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSRemovableVolumesUsageDescription</key>
<string>Camelid reads local model files from folders you select on external drives.</string>
</dict>
</plist>
4 changes: 1 addition & 3 deletions camelid-desktop/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
"minHeight": 560,
"center": true,
"backgroundColor": "#0e1216",
"url": "index.html",
"titleBarStyle": "Overlay",
"hiddenTitle": true
"url": "index.html"
}
],
"security": {
Expand Down
16 changes: 2 additions & 14 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Notice } from './components/ui/Notice'
import { ConfirmDialog } from './components/ui/ConfirmDialog'
import { isFirstRunHost } from './lib/firstRunActivation'
import { formatPreview, formatSidebarDate } from './lib/formatters'
import { hasOverlayTitleBar } from './lib/desktopShell'
import { useDashboardData } from './hooks/useDashboardData'
import { useBackendLauncher } from './hooks/useBackendLauncher'
import { useNotice } from './hooks/useNotice'
Expand Down Expand Up @@ -246,14 +245,10 @@ function App() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadDashboard])

/* Above the loading early-return: every hook must run on every render, and
the shell this window lives in cannot change while it is open. */
const [overlayTitleBar] = useState(hasOverlayTitleBar)

if (!dashboard) {
return (
<div className="loading-shell">
<div className="loading-shell-stack">
<div className="loading-shell" data-tauri-drag-region>
<div className="loading-shell-stack" data-tauri-drag-region>
<Notice notice={notice} tone={noticeTone} />
<div>Loading Camelid…</div>
</div>
Expand All @@ -266,17 +261,10 @@ function App() {
sidebarCollapsed ? 'is-collapsed' : '',
mobileNavOpen ? 'is-mobile-open' : '',
DEMO_UI ? 'is-demo' : '',
overlayTitleBar ? 'has-overlay-chrome' : '',
].filter(Boolean).join(' ')

return (
<div className={shellClasses}>
{/* macOS desktop only: the window draws no title bar of its own, so the
traffic lights float over the top-left of our content. This strip is
the room they sit in and the surface the window is dragged by —
without it the app showed the OS bar stacked above our own header,
which is what makes an app look like a web page in a frame. */}
{overlayTitleBar && <div className="app-drag-strip" data-tauri-drag-region />}
{!DEMO_UI && (
<SidebarRail
collapsed={!isMobile && sidebarCollapsed}
Expand Down
22 changes: 1 addition & 21 deletions frontend/src/lib/desktopShell.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,5 @@
/* Desktop-shell detection, shared so the browser build never pays for it.

Two different questions get asked about the desktop app and they are not the
same: whether we are inside Tauri at all (BackendBanner asks this, to know a
page cannot restart its own engine), and whether this window draws its own
title bar. Only macOS uses the overlay style, where the traffic lights float
over our content instead of sitting in a strip the OS owns — so only macOS
needs the window to reserve room for them and provide a drag region. */
/* Desktop-shell detection, shared so the browser build never pays for it. */
export function isDesktopShell() {
if (typeof window === 'undefined') return false
return Boolean(window.__TAURI__?.core?.invoke)
}

export function isMacPlatform() {
if (typeof navigator === 'undefined') return false
const platform = navigator.userAgentData?.platform || navigator.platform || ''
return /mac/i.test(platform)
}

/* True only where the window chrome is ours to draw. Keep this the single
source: styling that assumes the traffic lights are overlapping content must
never switch on in the browser build, where there are none. */
export function hasOverlayTitleBar() {
return isDesktopShell() && isMacPlatform()
}
17 changes: 0 additions & 17 deletions frontend/src/styles/shell.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,6 @@
.camelid-app.is-collapsed { grid-template-columns: var(--rail-collapsed-width) minmax(0, 1fr); }
.camelid-app.is-demo { grid-template-columns: minmax(0, 1fr); }

/* ---- macOS desktop window chrome ----
With the overlay title bar the OS draws no bar of its own, so the traffic
lights land on top of whatever occupies the window's top-left — the sidebar
brand row. The shell gains one title-bar's worth of room at the top and a
transparent strip across it that the window can be dragged by. Only children
carrying data-tauri-drag-region drag, so every control below stays clickable.
Applied nowhere but the macOS desktop shell; the browser build never sees it. */
.camelid-app.has-overlay-chrome { padding-top: var(--overlay-titlebar-height); }
.app-drag-strip {
position: fixed;
inset: 0 0 auto 0;
height: var(--overlay-titlebar-height);
z-index: 60;
/* Same ground as the shell so the strip reads as window chrome, not a gap. */
background: var(--color-canvas);
}

/* ---- Main column ---- */
.camelid-main {
display: flex;
Expand Down
23 changes: 20 additions & 3 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32917,8 +32917,14 @@ async fn local_models(
generation_capable: false,
context_length: None,
};
match read_metadata(&path) {
Ok(gguf) => {
let metadata_path = path.clone();
let parsed = tokio::time::timeout(
Duration::from_secs(5),
tokio::task::spawn_blocking(move || read_metadata(&metadata_path)),
)
.await;
match parsed {
Ok(Ok(Ok(gguf))) => {
c.embedding_capable = is_embedding_model(&gguf);
c.generation_capable = is_generation_model(&gguf);
let quant = crate::runnable::headline_quant_of(&gguf);
Expand Down Expand Up @@ -32951,7 +32957,18 @@ async fn local_models(
}
}
}
Err(err) => c.admission_reason = Some(format!("GGUF parse failed: {err}")),
Ok(Ok(Err(err))) => {
c.admission_reason = Some(format!("GGUF parse failed: {err}"));
}
Ok(Err(err)) => {
c.admission_reason = Some(format!("GGUF metadata task failed: {err}"));
}
Err(_) => {
c.admission_reason = Some(
"GGUF metadata scan timed out; removable-volume access may still need approval"
.to_string(),
);
}
}
local_meta_cache()
.lock()
Expand Down
Loading