Skip to content
Merged
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
40 changes: 31 additions & 9 deletions INSTALLATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Local model work requires a capability or worker extra.
|---|---|---|
| CLI or MCP | [uv 0.12+](https://docs.astral.sh/uv/getting-started/installation/) | Python and the isolated VidXP environment |
| Desktop-managed target | A supported OS, internet access for first setup, FFmpeg, ffprobe, `libx264`, and `aac` | uv, Python, VidXP, and selected model files |
| Desktop with existing target | A compatible local `vidxp` executable and that installation's own media-runtime setup | Target discovery and launch coordination only |
| Desktop with existing target | A compatible local `vidxp` executable and that installation's own media-runtime setup | Target discovery, service controls, and feature reinstallation for isolated uv tools |
| Docker | Docker Engine or Docker Desktop | Python, VidXP, and FFmpeg inside the image |

Native CLI and desktop processing require FFmpeg, ffprobe, `libx264`, and
Expand Down Expand Up @@ -354,27 +354,49 @@ does not install anything before that choice:

- **Use an existing installation** discovers compatible `vidxp` executables or
lets you browse to one. Desktop validates the versioned probe and launch
contracts, but the installation stays externally owned. Desktop never
installs, repairs, updates, removes, or broadly stops it. If its browser
surface is missing, enable the `frontend` extra with that installation's own
package-management workflow before Desktop can open it.
contracts, and the installation stays selected and externally owned.
For an isolated uv-tool installation, **Setup options** can change its search,
local-processing, browser, AI-assistant, or app-integration features. Desktop recreates that app environment at
its compatible VidXP and Python versions with the complete selected extra set,
then rechecks it. If the saved installation predates the required management
contract, Desktop offers to update that same uv-tool environment to the runtime
version bundled with the Desktop release before applying the chosen features.
It does not interpret fields missing from an older probe as disabled features.
Other environment types stay with their original package
manager. Desktop does not broadly stop an external installation. The
compatibility probe reports installed search, processing, and integration features.
- **Set up VidXP for me** creates a private Python and VidXP runtime owned by
Desktop. Python and uv do not need to be installed separately. Capability
code, the optional browser interface, model storage, and initial model
preparation are selected before applying the draft.
code, optional local video processing, browser interface, AI-assistant
integration, and app integration service,
model storage, and initial model preparation are selected before applying
the draft.

A managed setup or update remains a draft until its candidate runtime passes
the Desktop probe and launch contracts. Activation then replaces the previous
managed target atomically; failed or cancelled work leaves the previous target
authoritative. For an unchanged ready runtime, **Prepare / verify models**
authoritative. For an unchanged ready runtime, **Check downloaded models**
checks cached files and downloads only missing selected model material without
requiring a configuration change.

The active-target panel can run the selected installation's read-only
`vidxp doctor --json` check, start/monitor/stop local video processing through
the existing worker supervisor, generate `mcpServers` JSON bound to that exact
installation and repository, and start/monitor/stop a Desktop-owned loopback
`vidxp-api` process when the app integration service is installed. These controls remain
available after installation; Desktop is not only a first-run installer or a
browser launcher. It broadly stops only a Desktop-owned target and only
reinstalls an existing isolated tool after the user confirms the feature change.
Browser and app-service processes start private to the current computer.
Desktop can also invoke each service's existing `--share` mode: it shows the
resolved LAN port and URLs, warns that the shared browser has no authentication,
and exposes the API/MCP bearer token behind the connection details.

Starting Desktop, or starting it a second time, shows and focuses the control
panel without opening a browser. **Open VidXP** explicitly starts or reuses the
loopback browser service and opens one tab. Closing a configured window hides
it to the tray. Tray actions are **Manage VidXP**, **Open VidXP**, and **Quit
VidXP**. Quit stops the exact browser service Desktop launched; broad worker
VidXP**. Quit stops the exact browser and API services Desktop launched; broad worker
shutdown is limited to a Desktop-owned runtime.

The NSIS, DMG, and AppImage packages do not bundle FFmpeg. Managed setup can
Expand Down
20 changes: 19 additions & 1 deletion desktop/runtime-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,29 @@
"python_version": "3.14.6",
"uv_version": "0.12.0",
"surfaces": {
"worker": {
"extra": "local-worker",
"label": "Process videos on this computer",
"description": "Run background indexing, search, and grounded questions locally. This includes all built-in search features and is the normal desktop setup.",
"default": true
},
"browser": {
"extra": "frontend",
"label": "Browser interface",
"description": "Installs the local browser interface. Leave this off for a processing-only runtime.",
"description": "Use VidXP's visual workspace in your default browser. It stays private to this computer unless you explicitly share it without authentication.",
"default": true
},
"mcp": {
"extra": "mcp",
"label": "AI assistant integration",
"description": "Use VidXP from an MCP-compatible AI assistant installed on this computer.",
"default": false
},
"server": {
"extra": "server",
"label": "App integration service",
"description": "Run an API and network-style MCP connection for other software. It is private by default and can be shared on your local network with bearer-token authentication.",
"default": false
}
},
"capabilities": {
Expand Down
22 changes: 21 additions & 1 deletion desktop/src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ fn main() {
"local-worker",
"--extra",
"frontend",
"--extra",
"server",
"--no-dev",
"--no-emit-project",
"--no-hashes",
Expand Down Expand Up @@ -90,5 +92,23 @@ fn main() {
println!("cargo:rerun-if-changed=../../uv.lock");
println!("cargo:rerun-if-changed=../runtime-manifest.json");

tauri_build::build()
let mut attributes = tauri_build::Attributes::new();
#[cfg(windows)]
{
attributes = attributes
.windows_attributes(tauri_build::WindowsAttributes::new_without_app_manifest());
add_windows_manifest();
}
tauri_build::try_build(attributes).expect("Tauri build configuration must be valid")
}

#[cfg(windows)]
fn add_windows_manifest() {
let manifest = std::path::PathBuf::from(
std::env::var_os("CARGO_MANIFEST_DIR").expect("Cargo must provide CARGO_MANIFEST_DIR"),
)
.join("windows-app-manifest.xml");
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rustc-link-arg=/MANIFEST:EMBED");
println!("cargo:rustc-link-arg=/MANIFESTINPUT:{}", manifest.display());
}
19 changes: 13 additions & 6 deletions desktop/src-tauri/src/browser_readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ struct ReadinessMarker {
port: u16,
#[serde(rename = "pid")]
_pid: u32,
#[serde(default)]
network_url: Option<String>,
}

fn marker_matches(contents: &[u8], nonce: &str, port: u16) -> bool {
Expand Down Expand Up @@ -66,7 +68,7 @@ pub fn wait_for_browser_readiness(
port: u16,
deadline: Instant,
cancellation: &CancellationToken,
) -> Result<(), String> {
) -> Result<Option<String>, String> {
let address = SocketAddr::from(([127, 0, 0, 1], port));
while Instant::now() < deadline {
if cancellation.is_cancelled() {
Expand All @@ -83,11 +85,14 @@ pub fn wait_for_browser_readiness(
"The VidXP interface exited during startup ({status})."
));
}
if fs::read(marker_path).is_ok_and(|contents| {
marker_matches(&contents, nonce, port) && streamlit_health_is_ready(address)
}) {
if let Ok(contents) = fs::read(marker_path)
&& marker_matches(&contents, nonce, port)
&& streamlit_health_is_ready(address)
{
let marker = serde_json::from_slice::<ReadinessMarker>(&contents)
.map_err(|error| format!("The interface readiness marker is invalid: {error}"))?;
let _ = fs::remove_file(marker_path);
return Ok(());
return Ok(marker.network_url);
}
thread::sleep(Duration::from_millis(50));
}
Expand Down Expand Up @@ -231,11 +236,12 @@ mod tests {
"nonce": "launch",
"port": port,
"pid": process.id() + 1,
"network_url": format!("http://192.168.1.20:{port}"),
})
.to_string(),
)
.expect("marker");
wait_for_browser_readiness(
let network_url = wait_for_browser_readiness(
&mut process,
&marker,
"launch",
Expand All @@ -244,6 +250,7 @@ mod tests {
&CancellationToken::default(),
)
.expect("ready");
assert_eq!(network_url, Some(format!("http://192.168.1.20:{port}")));
assert!(!marker.exists());
server.join().expect("server");
}
Expand Down
Loading