Skip to content
Open
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,7 @@ cython_debug/
# The large model files
*.bin
*.gguf
*.onnx
*.onnx

# pyapp build directory
pyapp-latest/
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,16 @@ To use these new settings, use the command:

uv run glados start --config configs/assistant_config.yaml

## Build releases

This project can be packaged into a single executable using [pyapp](https://github.com/ofek/pyapp). The resulting binary is fully self-contained and automatically installs all required dependencies.

To build releases, cd to this project root directory and run `./scripts/compile_pyapp.sh`.
This will produce the following executables (version 0.1.0 in this example):
- dist/GLaDOS-0.1.0-windows-x86_64.exe
- dist/GLaDOS-0.1.0-linux-x86_64
- dist/GLaDOS-0.1.0-linux-x86_64.AppImage

## Common Issues
1. If you find you are getting stuck in loops, as GLaDOS is hearing herself speak, you have two options:
1. Solve this by upgrading your hardware. You need to you either headphone, so she can't physically hear herself speak, or a conference-style room microphone/speaker. These have hardware sound cancellation, and prevent these loops.
Expand Down
Binary file added images/aperture_icon.ico
Binary file not shown.
Binary file added images/aperture_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ dependencies = [
"pydantic>=2.10.6",
"httpx>=0.28.1",
"rich>=14.0.0",
"onnxruntime-gpu>=1.16.0"
]

[project.optional-dependencies]
cuda = ["onnxruntime-gpu>=1.16.0"]
cpu = ["onnxruntime>=1.16.0"]
dev = [
"pytest",
"pytest-anyio",
Expand All @@ -40,6 +39,9 @@ glados = "glados.cli:main"
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/glados"]

# Ruff configuration
[tool.ruff]
# Same as Black
Expand Down
176 changes: 176 additions & 0 deletions scripts/compile_pyapp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#!/bin/bash

set -e # Stop on error
set -x # Echo commands

# Assert some commands are installed
for cmd in uv rustup docker wine; do
if ! command -v "$cmd" &> /dev/null; then
echo "$cmd could not be found, please install it first."
exit 1
fi
done

# Assert script is run on a linux amd64 host
if [[ "$(uname -s)" != "Linux" || "$(uname -m)" != "x86_64" ]]; then
echo "This script must be run on a Linux amd64 host."
exit 1
fi

# Variables
PROJECT_NAME="glados"
BINARY_NAME="GLaDOS"
VERSION="0.1.0"
EXE_BASE="${BINARY_NAME}-${VERSION}"
WHEEL_PATH="$(pwd)/dist/${PROJECT_NAME}-${VERSION}-py3-none-any.whl"
COMMENT="Genetic Lifeform and Disk Operating System"
APPDIR="dist/glados.AppDir"

# Export PYAPP variables
export PYAPP_PROJECT_NAME="${PROJECT_NAME}"
export PYAPP_PROJECT_VERSION="${VERSION}"
export PYAPP_PROJECT_PATH="${WHEEL_PATH}"
export PYAPP_EXEC_MODULE="${PROJECT_NAME}"
export PYAPP_PYTHON_VERSION="3.12"
export PYAPP_DISTRIBUTION_EMBED=1

# Ensure cross-rs is installed
cargo install cross --git https://github.com/cross-rs/cross

# Download pyapp source if not present
if [ ! -d "pyapp-latest" ]; then
curl -L https://github.com/ofek/pyapp/releases/latest/download/source.tar.gz | tar -xz
mv pyapp-v* pyapp-latest

# Here we create a Cross.toml config where we say that we want to mount the wheel file
# inside the container that cross-rs will create, this is needed because during the
# compilation of pyapp, it uses the wheel file to create the final binary.
# The way it works is that we give the name of the environnement variable that cross-rs
# will evaluate to create the volume
cat <<EOF > "pyapp-latest/Cross.toml"
[build.env]
volumes = ["PYAPP_PROJECT_PATH"]
EOF
fi

# Clean up previous builds to avoid potential issues
rm -rf dist/$EXE_BASE-*
rm -rf "${WHEEL_PATH}"
rm -rf "${APPDIR}"

# Build wheel
uv sync
uv build --wheel

# Cross-compile targets: os arch target ext
targets=(
# We choose gnu because musl doesn't work with onnxruntime-gpu
"x86_64-pc-windows-gnu"
"x86_64-unknown-linux-gnu"

# I did not manage to get this working :(
# "aarch64-unknown-linux-gnu"
)

# Build for each target if binary not present
for target in "${targets[@]}"; do
# Split target into array by '-'
IFS='-' read -r arch _ os _ <<< "$target"

# Determine extension
if [[ "$os" == "windows" ]]; then
ext=".exe"
else
ext=""
fi

# Install missing Rust targets (necessary ?)
if ! rustup target list --installed | grep -q "^${target}$"; then
rustup target add "${target}"
fi

pushd pyapp-latest
CROSS_USE_SYSTEM_LIBS=0 cross build --release --target "${target}"
popd
cp "pyapp-latest/target/${target}/release/pyapp${ext}" "dist/${EXE_BASE}-${os}-${arch}${ext}"
done

# Download appimagetool if not present
appimagetool_file="appimagetool-x86_64.AppImage"
if [ ! -f "dist/${appimagetool_file}" ]; then
curl -L "https://github.com/AppImage/appimagetool/releases/download/continuous/${appimagetool_file}" -o "dist/${appimagetool_file}"
fi
chmod +x "dist/${appimagetool_file}"

# Create AppDir
mkdir -p "${APPDIR}/usr/bin"
mkdir -p "${APPDIR}/usr/share/icons/hicolor/256x256/apps/"
mkdir -p "${APPDIR}/usr/share/metainfo/"

# Copy binary
cp "dist/${BINARY_NAME}-${VERSION}-linux-x86_64" "${APPDIR}/usr/bin/${BINARY_NAME}"

# Desktop file
cat <<EOF > "${APPDIR}/glados.desktop"
[Desktop Entry]
Type=Application
Name=${BINARY_NAME}
X-AppImage-Version=${VERSION}
Comment=${COMMENT}
Exec=AppRun
Icon=aperture_icon
Categories=Utility;
Terminal=false
EOF

# AppData metadata
cat <<EOF > "${APPDIR}/usr/share/metainfo/io.github.glados.metainfo.xml"
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application" license="MIT">
<id>io.github.glados</id>
<name>GLaDOS</name>
<summary>Genetic Lifeform and Disk Operating System</summary>
<content_rating type="oars-1.1"/>
<developer_name>dnhkng</developer_name>
<url type="homepage">https://github.com/dnhkng/GLaDOS</url>
<description>
<p>
This is the AI system of a project dedicated to building a real-life version of GLaDOS.
One of the initial objectives is to have a low-latency platform, where GLaDOS can respond to voice interactions within 600ms.
The ultimate goal is to have an aware, interactive, and embodied GLaDOS.
</p>
</description>
<launchable type="desktop-id">glados.desktop</launchable>
<releases>
<release version="${VERSION}" date="$(date +%Y-%m-%d)"/>
</releases>
</component>
EOF

# Icons (we need to copy it 2 times in order for it to work properly)
cp images/aperture_icon.png "${APPDIR}/aperture_icon.png"
cp images/aperture_icon.png "${APPDIR}/usr/share/icons/hicolor/256x256/apps/aperture_icon.png"

# AppRun
cat <<'EOF' > "${APPDIR}/AppRun"
#!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")"
x-terminal-emulator -e "$HERE/usr/bin/GLaDOS" "$@"
EOF
chmod +x "${APPDIR}/AppRun"

# Build AppImage
"./dist/${appimagetool_file}" "${APPDIR}" "dist/GLaDOS-${VERSION}-linux-x86_64.AppImage"

# Modify metadata of windows binary
if [ ! -f dist/rcedit-x64.exe ]; then
wget https://github.com/electron/rcedit/releases/download/v1.1.1/rcedit-x64.exe -O dist/rcedit-x64.exe
fi
echo "Executing a wine command, this may cause your PC to freeze for a minute"
wine dist/rcedit-x64.exe "dist/${EXE_BASE}-windows-x86_64.exe" \
--set-icon "images/aperture_icon.ico" \
--set-version-string "FileDescription" "${COMMENT}" \
--set-version-string "ProductName" "${BINARY_NAME}" \
--set-version-string "CompanyName" "dnhkng" \
--set-file-version "${VERSION}" \
--set-product-version "${VERSION}"
5 changes: 5 additions & 0 deletions src/glados/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""GLaDOS - Voice Assistant using ONNX models for speech synthesis and recognition."""

import os

if os.getenv("PYAPP") == "1":
os.environ["PYAPP_RELATIVE_DIR"] = os.getcwd()

from .core.engine import Glados, GladosConfig

__version__ = "0.1.0"
Expand Down
4 changes: 4 additions & 0 deletions src/glados/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .cli import main

if __name__ == "__main__":
main()
7 changes: 7 additions & 0 deletions src/glados/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from hashlib import sha256
from pathlib import Path
import sys
import os

import httpx
from rich import print as rprint
Expand All @@ -13,6 +14,7 @@
from .TTS import tts_glados
from .utils import spoken_text_converter as stc
from .utils.resources import resource_path
from .utils.automated_install import main as automated_install

# Type aliases for clarity
type FileHash = str
Expand Down Expand Up @@ -286,6 +288,11 @@ def main() -> int:
if args.command == "download":
return asyncio.run(download_models())
else:
if os.getenv("PYAPP") == "1":
automated_install()
tui(DEFAULT_CONFIG)
return 0

if not models_valid():
print("Some model files are invalid or missing. Please run 'uv run glados download'")
return 1
Expand Down
Binary file added src/glados/default_configs/0.wav
Binary file not shown.
Loading