[GH-ISSUE #11363] Linux: support for new ollama settings #33257

Closed
opened 2026-04-22 15:46:22 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @olumolu on GitHub (Jul 10, 2025).
Original GitHub issue: https://github.com/ollama/ollama/issues/11363

New macOS and Windows app settings with Ollama 0.9.5!
but not yet implemented in linux
https://x.com/ollama/status/1940517540222194141

Originally created by @olumolu on GitHub (Jul 10, 2025). Original GitHub issue: https://github.com/ollama/ollama/issues/11363 New macOS and Windows app settings with Ollama 0.9.5! but not yet implemented in linux https://x.com/ollama/status/1940517540222194141
GiteaMirror added the feature request label 2026-04-22 15:46:22 -05:00
Author
Owner

@rick-github commented on GitHub (Jul 10, 2025):

There are no new ollama settings, the Windows/macOS apps are just UI interfaces for the same settings: OLLAMA_HOST and OLLAMA_MODELS.

<!-- gh-comment-id:3058849067 --> @rick-github commented on GitHub (Jul 10, 2025): There are no new ollama settings, the Windows/macOS apps are just UI interfaces for the same settings: `OLLAMA_HOST` and `OLLAMA_MODELS`.
Author
Owner

@rick-github commented on GitHub (Jul 11, 2025):

ollama-settings.py
#!/usr/bin/env python3

import os
import stat
import subprocess
import sys
import tempfile
import argparse

from pathlib import Path
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                            QHBoxLayout, QLabel, QPushButton, QLineEdit, 
                            QFileDialog, QMessageBox, QFrame, QScrollArea)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont, QPainter, QPen, QBrush, QPixmap, QIcon

class ModernToggleSwitch(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(50, 24)
        self.checked = False
        self.animation_duration = 150
        self.parent = parent
        
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.checked = not self.checked
            self.update()
            if hasattr(self.parent, 'on_expose_toggle'):
                self.parent.on_expose_toggle()
    
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # Draw track
        track_rect = self.rect().adjusted(2, 2, -2, -2)
        track_radius = track_rect.height() // 2
        
        if self.checked:
            painter.setBrush(QBrush(Qt.darkGray))
        else:
            painter.setBrush(QBrush(Qt.lightGray))
        
        painter.setPen(Qt.NoPen)
        painter.drawRoundedRect(track_rect, track_radius, track_radius)
        
        # Draw thumb
        thumb_radius = track_radius - 2
        if self.checked:
            thumb_x = track_rect.right() - thumb_radius - 2
        else:
            thumb_x = track_rect.left() + thumb_radius + 2
        
        thumb_y = track_rect.center().y()
        painter.setBrush(QBrush(Qt.white))
        painter.drawEllipse(thumb_x - thumb_radius, thumb_y - thumb_radius, 
                          thumb_radius * 2, thumb_radius * 2)


class OllamaSettings(QMainWindow):
    def __init__(self):
        super().__init__()

        parser = argparse.ArgumentParser()
        parser.add_argument("--restart", help="Restart ollama after updating settings", action='store_true', default=False)
        parser.add_argument("--reset", help="Reset ollama config to default", action='store_true', default=False)
        parser.add_argument("--config-dir", help="Ollama configuration directory", default="/etc/systemd/system/ollama.service.d")
        parser.add_argument("--models-dir", help="Default models directory", default="/usr/share/ollama/.ollama/models")
        parser.add_argument("--host", help="Default OLLAMA_HOST value", default="127.0.0.1:11434")
        self.args = parser.parse_args()

        self.env = {}
        if not self.args.reset:
            self.env = self.read_environment(self.args.config_dir)
        self.env["OLLAMA_HOST"] = self.env.get("OLLAMA_HOST", self.args.host)
        self.env["OLLAMA_MODELS"] = self.env.get("OLLAMA_MODELS", self.args.models_dir)

        self.init_ui()

        host, _ = self.split_ollama_host(self.env["OLLAMA_HOST"])
        self.expose_enabled = True
        if host == "127.0.0.1":
            self.expose_enabled = False
        self.toggle_switch.checked = self.expose_enabled
        self.toggle_switch.update()
        self.location_entry.setText(self.env["OLLAMA_MODELS"])
        
    def closeEvent(self, event):
        _, port = self.split_ollama_host(self.env["OLLAMA_HOST"])
        host = "0.0.0.0" if self.expose_enabled else "127.0.0.1"
        env = {
          "OLLAMA_MODELS": self.location_entry.text(),
          "OLLAMA_HOST": f"{host}:{port}"
        }
        self.write_environment(self.args.config_dir, env)

    def split_ollama_host(sel, ollama_host):
        ollama_host = ollama_host.split("://", 1)[1] if "://" in ollama_host else ollama_host
        ollama_host = ollama_host.split("/", 1)[0] if "/" in ollama_host else ollama_host
        host, port = ollama_host.split(":") if ":" in ollama_host else ("127.0.0.1", "11434")
        host = host or "127.0.0.1"
        port = port or "11434"
        return host, port

    def read_environment(self, path):
        env = {}
        directory = Path(path)
        if not directory.exists():
            return env
        files = sorted([f for f in directory.iterdir() if f.is_file()])
        for file_path in files:
            service_lines = []
            in_service_section = False
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    for line in file:
                        line = line.strip()
                        if line == '[Service]':
                            in_service_section = True
                            continue
                        elif line.startswith('[') and line.endswith(']') and line != '[Service]':
                            in_service_section = False
                            continue
                        if in_service_section and line and line.startswith('Environment='):
                            if len(line) == 12:
                                env = {}
                                continue
                            name = ''
                            value = ''
                            in_name = True
                            quoted = False
                            ci = line[12:].__iter__()
                            for _, c in enumerate(ci):
                                if c == '"' and quoted:
                                    quoted = False
                                    continue
                                elif c == '"' and not quoted:
                                    quoted = True
                                    continue
                                elif c == '\\':
                                    c = ci.__next__()
                                elif c == ' ' and not quoted:
                                    if len(name) and len(value):
                                        env[name] = value
                                    in_name = True
                                    name = ''
                                    value = ''
                                    continue
                                elif c == '=' and in_name:
                                    in_name = False
                                    continue
                                if in_name:
                                    name += c
                                else:
                                    value += c
                            if len(name) and len(value):
                                env[name] = value
            except:
                pass
        return env

    def write_environment(self, path, env):
        config = "# This file is generated, do not edit\n"
        config += "[Service]\n"
        for k, v in env.items():
            env = v.replace('"', '\\"')
            config += f'Environment={k}="{env}"\n'
        try:
            directory = Path(path)
            if not directory.exists():
                subprocess.run(['sudo', 'mkdir', '-p', directory], check=True)
            tmp = tempfile.NamedTemporaryFile(delete=False)
            with open(tmp.name, 'w') as f:
                f.write(config)
            os.chmod(tmp.name, stat.S_IWUSR|stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH)
            subprocess.run(['sudo', 'mv', '-f', tmp.name, f'{directory}/zz_settings.conf' ], check=True)
            subprocess.run(['sudo', 'systemctl', 'daemon-reload'], check=True)
            if self.args.restart:
                subprocess.run(['sudo', 'systemctl', 'restart', 'ollama'], check=True)
        except subprocess.CalledProcessError as e:
            print(f"Error updating configuration: {e}")
            print("Make sure you have sudo privileges")

    def check_file(self, path, fallback):
        if os.path.exists(path) and os.access(path, os.R_OK):
            return path
        return fallback

    def init_ui(self):
        # Set window properties
        self.setWindowTitle("Settings")
        self.setGeometry(100, 100, 650, 500)
        self.setMinimumSize(600, 550)
        
        # Create central widget and main layout
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # Create scroll area
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        scroll_area.setFrameShape(QFrame.NoFrame)
        
        # Main content widget
        content_widget = QWidget()
        main_layout = QVBoxLayout(content_widget)
        main_layout.setSpacing(0)
        main_layout.setContentsMargins(30, 30, 30, 30)
        
        # Title
        title_label = QLabel("Settings")
        title_font = QFont("Segoe UI", 24, QFont.Normal)
        title_label.setFont(title_font)
        title_label.setStyleSheet("color: #2c2c2c; margin-bottom: 30px;")
        main_layout.addWidget(title_label)
        
        # Expose section
        icon = self.check_file("/usr/share/icons/Humanity/apps/22/wifi-100.svg", "📡")
        expose_section = self.create_setting_section(
            icon,
            "Expose", "Allow other devices or services to access.",
            has_toggle=True
        )
        main_layout.addWidget(expose_section)
        
        # Location section - example with custom browse icon
        icon = self.check_file("/usr/share/icons/Humanity/places/22/folder.svg", "📁")
        location_section = self.create_setting_section(
            icon,
            "Model location", "Location where models are stored.",
            has_file_input=True,
            browse_icon=icon
        )
        main_layout.addWidget(location_section)
        
        # Add some spacing
        main_layout.addSpacing(30)
        
        # Reset button
        reset_button = QPushButton("Reset to defaults")
        reset_button.clicked.connect(self.clear_inputs)
        reset_button.setStyleSheet("""
            QPushButton {
                background-color: #f5f5f5;
                border: 1px solid #d1d1d1;
                border-radius: 6px;
                padding: 8px 16px;
                font-size: 14px;
                color: #333;
                font-weight: 500;
            }
            QPushButton:hover {
                background-color: #e8e8e8;
                border-color: #b8b8b8;
            }
            QPushButton:pressed {
                background-color: #d8d8d8;
            }
        """)
        reset_layout = QHBoxLayout()
        reset_layout.addStretch()
        reset_layout.addWidget(reset_button)
        main_layout.addLayout(reset_layout)
        
        # Add stretch at the bottom
        main_layout.addStretch()
        
        # Set up scroll area
        scroll_area.setWidget(content_widget)
        
        # Main window layout
        window_layout = QVBoxLayout(central_widget)
        window_layout.setContentsMargins(0, 0, 0, 0)
        window_layout.addWidget(scroll_area)
        
        # Apply global styling
        self.setStyleSheet("""
            QMainWindow {
                background-color: #fafafa;
            }
            QScrollArea {
                background-color: #fafafa;
                border: none;
            }
            QWidget {
                background-color: #fafafa;
            }
        """)
        
    def create_setting_section(self, icon, title, description, has_toggle=False, has_file_input=False, browse_icon="📁"):
        # Main section widget
        section_widget = QWidget()
        section_widget.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 8px;
                margin-bottom: 16px;
            }
        """)
        
        # Create main vertical layout for the section
        main_layout = QVBoxLayout(section_widget)
        main_layout.setContentsMargins(20, 20, 20, 20)
        main_layout.setSpacing(12)
        
        # Top row with icon, text, and toggle (if applicable)
        top_row_layout = QHBoxLayout()
        top_row_layout.setSpacing(16)
        
        # Icon - handle both emoji strings and image file paths
        icon_label = QLabel()
        icon_label.setFixedSize(48, 48)
        icon_label.setAlignment(Qt.AlignCenter)
        icon_label.setStyleSheet("background-color: transparent;")
        
        # Check if icon is a file path
        if isinstance(icon, str) and (icon.endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg')) or os.path.isfile(icon)):
            # Load image from file path
            pixmap = QPixmap(icon)
            if not pixmap.isNull():
                # Scale the image to fit the icon size while maintaining aspect ratio
                scaled_pixmap = pixmap.scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                icon_label.setPixmap(scaled_pixmap)
            else:
                # Fallback to default emoji if image fails to load
                icon_label.setText("📄")
                icon_label.setFont(QFont("Segoe UI", 16))
        else:
            # Treat as emoji or text
            icon_label.setText(icon)
            icon_label.setFont(QFont("Segoe UI", 16))
        
        top_row_layout.addWidget(icon_label)
        
        # Text content
        text_layout = QVBoxLayout()
        text_layout.setSpacing(4)
        
        title_label = QLabel(title)
        title_label.setFont(QFont("Segoe UI", 15, QFont.Medium))
        title_label.setStyleSheet("color: #1a1a1a; background-color: transparent;")
        text_layout.addWidget(title_label)
        
        desc_label = QLabel(description)
        desc_label.setFont(QFont("Segoe UI", 13))
        desc_label.setStyleSheet("color: #6b6b6b; background-color: transparent;")
        desc_label.setWordWrap(True)
        desc_label.setMinimumWidth(400)  # Set minimum width for description
        text_layout.addWidget(desc_label)
        
        top_row_layout.addLayout(text_layout, 1)  # Give text layout stretch factor of 1
        top_row_layout.addStretch(0)  # Reduce stretch to give more space to text
        
        # Add toggle switch if needed
        if has_toggle:
            self.toggle_switch = ModernToggleSwitch(self)
            top_row_layout.addWidget(self.toggle_switch)
        
        # Add the top row to main layout
        main_layout.addLayout(top_row_layout)
        
        # Add file input section if needed (under the description)
        if has_file_input:
            # File input layout
            file_input_layout = QHBoxLayout()
            file_input_layout.setSpacing(8)
            file_input_layout.setContentsMargins(40, 0, 0, 0)  # Indent to align with text
            
            self.location_entry = QLineEdit()
            self.location_entry.setPlaceholderText("Select a file...")
            self.location_entry.setText("")
            self.location_entry.setStyleSheet("""
                QLineEdit {
                    background-color: #f8f8f8;
                    border: 1px solid #e0e0e0;
                    border-radius: 6px;
                    padding: 8px 12px;
                    font-size: 14px;
                    color: #333;
                    min-width: 300px;
                }
                QLineEdit:focus {
                    border-color: #007acc;
                    background-color: white;
                }
            """)
            file_input_layout.addWidget(self.location_entry)
            
            # Create browse button with custom icon
            browse_button_text = "Browse"
            if isinstance(browse_icon, str) and (browse_icon.endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg')) or os.path.isfile(browse_icon)):
                # Use image file for button icon
                self.browse_button = QPushButton(f" {browse_button_text}")
                
                # Load and set icon from file
                pixmap = QPixmap(browse_icon)
                if not pixmap.isNull():
                    # Scale icon for button (16x16 is good for buttons)
                    scaled_pixmap = pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    from PyQt5.QtGui import QIcon
                    button_icon = QIcon(scaled_pixmap)
                    self.browse_button.setIcon(button_icon)
                else:
                    # Fallback to emoji if image fails to load
                    self.browse_button.setText(f"📁 {browse_button_text}")
            else:
                # Use emoji or text icon
                self.browse_button = QPushButton(f"{browse_icon} {browse_button_text}")
            
            self.browse_button.clicked.connect(lambda: self.browse_file(icon_label))
            self.browse_button.setStyleSheet("""
                QPushButton {
                    background-color: #f5f5f5;
                    border: 1px solid #d1d1d1;
                    border-radius: 6px;
                    padding: 8px 16px;
                    font-size: 14px;
                    color: #333;
                    font-weight: 500;
                }
                QPushButton:hover {
                    background-color: #e8e8e8;
                    border-color: #b8b8b8;
                }
                QPushButton:pressed {
                    background-color: #d8d8d8;
                }
            """)
            file_input_layout.addWidget(self.browse_button)
            file_input_layout.addStretch()
            
            main_layout.addLayout(file_input_layout)
        
        # Adjust minimum height based on content
        if has_file_input:
            section_widget.setMinimumHeight(120)
        else:
            section_widget.setMinimumHeight(80)
        
        return section_widget
    
    def on_expose_toggle(self):
        """Handle expose toggle switch change"""
        self.expose_enabled = self.toggle_switch.checked
    
    def browse_file(self, icon_label=None):
        """Open file dialog to select a directory"""
        # Get current path
        current_path = self.location_entry.text() if self.location_entry.text() else ""
        
        # Open file dialog for directory selection
        directory = QFileDialog.getExistingDirectory(
            self,
            "Select Model Directory",
            current_path
        )
        
        if directory:
            self.location_entry.setText(directory)
    
    def clear_inputs(self):
        """Reset all inputs to defaults"""
        self.location_entry.setText(self.args.models_dir)
        self.toggle_switch.checked = False
        self.toggle_switch.update()
        self.expose_enabled = False

def main():
    app = QApplication(sys.argv)
    
    # Set application properties
    app.setApplicationName("Settings")
    app.setApplicationVersion("1.0")
    
    # Create and show main window
    window = OllamaSettings()
    window.show()
    
    # Run the application
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()
<!-- gh-comment-id:3060170979 --> @rick-github commented on GitHub (Jul 11, 2025): <details open> <summary>ollama-settings.py</summary> ```python #!/usr/bin/env python3 import os import stat import subprocess import sys import tempfile import argparse from pathlib import Path from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit, QFileDialog, QMessageBox, QFrame, QScrollArea) from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QFont, QPainter, QPen, QBrush, QPixmap, QIcon class ModernToggleSwitch(QWidget): def __init__(self, parent=None): super().__init__(parent) self.setFixedSize(50, 24) self.checked = False self.animation_duration = 150 self.parent = parent def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.checked = not self.checked self.update() if hasattr(self.parent, 'on_expose_toggle'): self.parent.on_expose_toggle() def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # Draw track track_rect = self.rect().adjusted(2, 2, -2, -2) track_radius = track_rect.height() // 2 if self.checked: painter.setBrush(QBrush(Qt.darkGray)) else: painter.setBrush(QBrush(Qt.lightGray)) painter.setPen(Qt.NoPen) painter.drawRoundedRect(track_rect, track_radius, track_radius) # Draw thumb thumb_radius = track_radius - 2 if self.checked: thumb_x = track_rect.right() - thumb_radius - 2 else: thumb_x = track_rect.left() + thumb_radius + 2 thumb_y = track_rect.center().y() painter.setBrush(QBrush(Qt.white)) painter.drawEllipse(thumb_x - thumb_radius, thumb_y - thumb_radius, thumb_radius * 2, thumb_radius * 2) class OllamaSettings(QMainWindow): def __init__(self): super().__init__() parser = argparse.ArgumentParser() parser.add_argument("--restart", help="Restart ollama after updating settings", action='store_true', default=False) parser.add_argument("--reset", help="Reset ollama config to default", action='store_true', default=False) parser.add_argument("--config-dir", help="Ollama configuration directory", default="/etc/systemd/system/ollama.service.d") parser.add_argument("--models-dir", help="Default models directory", default="/usr/share/ollama/.ollama/models") parser.add_argument("--host", help="Default OLLAMA_HOST value", default="127.0.0.1:11434") self.args = parser.parse_args() self.env = {} if not self.args.reset: self.env = self.read_environment(self.args.config_dir) self.env["OLLAMA_HOST"] = self.env.get("OLLAMA_HOST", self.args.host) self.env["OLLAMA_MODELS"] = self.env.get("OLLAMA_MODELS", self.args.models_dir) self.init_ui() host, _ = self.split_ollama_host(self.env["OLLAMA_HOST"]) self.expose_enabled = True if host == "127.0.0.1": self.expose_enabled = False self.toggle_switch.checked = self.expose_enabled self.toggle_switch.update() self.location_entry.setText(self.env["OLLAMA_MODELS"]) def closeEvent(self, event): _, port = self.split_ollama_host(self.env["OLLAMA_HOST"]) host = "0.0.0.0" if self.expose_enabled else "127.0.0.1" env = { "OLLAMA_MODELS": self.location_entry.text(), "OLLAMA_HOST": f"{host}:{port}" } self.write_environment(self.args.config_dir, env) def split_ollama_host(sel, ollama_host): ollama_host = ollama_host.split("://", 1)[1] if "://" in ollama_host else ollama_host ollama_host = ollama_host.split("/", 1)[0] if "/" in ollama_host else ollama_host host, port = ollama_host.split(":") if ":" in ollama_host else ("127.0.0.1", "11434") host = host or "127.0.0.1" port = port or "11434" return host, port def read_environment(self, path): env = {} directory = Path(path) if not directory.exists(): return env files = sorted([f for f in directory.iterdir() if f.is_file()]) for file_path in files: service_lines = [] in_service_section = False try: with open(file_path, 'r', encoding='utf-8') as file: for line in file: line = line.strip() if line == '[Service]': in_service_section = True continue elif line.startswith('[') and line.endswith(']') and line != '[Service]': in_service_section = False continue if in_service_section and line and line.startswith('Environment='): if len(line) == 12: env = {} continue name = '' value = '' in_name = True quoted = False ci = line[12:].__iter__() for _, c in enumerate(ci): if c == '"' and quoted: quoted = False continue elif c == '"' and not quoted: quoted = True continue elif c == '\\': c = ci.__next__() elif c == ' ' and not quoted: if len(name) and len(value): env[name] = value in_name = True name = '' value = '' continue elif c == '=' and in_name: in_name = False continue if in_name: name += c else: value += c if len(name) and len(value): env[name] = value except: pass return env def write_environment(self, path, env): config = "# This file is generated, do not edit\n" config += "[Service]\n" for k, v in env.items(): env = v.replace('"', '\\"') config += f'Environment={k}="{env}"\n' try: directory = Path(path) if not directory.exists(): subprocess.run(['sudo', 'mkdir', '-p', directory], check=True) tmp = tempfile.NamedTemporaryFile(delete=False) with open(tmp.name, 'w') as f: f.write(config) os.chmod(tmp.name, stat.S_IWUSR|stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH) subprocess.run(['sudo', 'mv', '-f', tmp.name, f'{directory}/zz_settings.conf' ], check=True) subprocess.run(['sudo', 'systemctl', 'daemon-reload'], check=True) if self.args.restart: subprocess.run(['sudo', 'systemctl', 'restart', 'ollama'], check=True) except subprocess.CalledProcessError as e: print(f"Error updating configuration: {e}") print("Make sure you have sudo privileges") def check_file(self, path, fallback): if os.path.exists(path) and os.access(path, os.R_OK): return path return fallback def init_ui(self): # Set window properties self.setWindowTitle("Settings") self.setGeometry(100, 100, 650, 500) self.setMinimumSize(600, 550) # Create central widget and main layout central_widget = QWidget() self.setCentralWidget(central_widget) # Create scroll area scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll_area.setFrameShape(QFrame.NoFrame) # Main content widget content_widget = QWidget() main_layout = QVBoxLayout(content_widget) main_layout.setSpacing(0) main_layout.setContentsMargins(30, 30, 30, 30) # Title title_label = QLabel("Settings") title_font = QFont("Segoe UI", 24, QFont.Normal) title_label.setFont(title_font) title_label.setStyleSheet("color: #2c2c2c; margin-bottom: 30px;") main_layout.addWidget(title_label) # Expose section icon = self.check_file("/usr/share/icons/Humanity/apps/22/wifi-100.svg", "📡") expose_section = self.create_setting_section( icon, "Expose", "Allow other devices or services to access.", has_toggle=True ) main_layout.addWidget(expose_section) # Location section - example with custom browse icon icon = self.check_file("/usr/share/icons/Humanity/places/22/folder.svg", "📁") location_section = self.create_setting_section( icon, "Model location", "Location where models are stored.", has_file_input=True, browse_icon=icon ) main_layout.addWidget(location_section) # Add some spacing main_layout.addSpacing(30) # Reset button reset_button = QPushButton("Reset to defaults") reset_button.clicked.connect(self.clear_inputs) reset_button.setStyleSheet(""" QPushButton { background-color: #f5f5f5; border: 1px solid #d1d1d1; border-radius: 6px; padding: 8px 16px; font-size: 14px; color: #333; font-weight: 500; } QPushButton:hover { background-color: #e8e8e8; border-color: #b8b8b8; } QPushButton:pressed { background-color: #d8d8d8; } """) reset_layout = QHBoxLayout() reset_layout.addStretch() reset_layout.addWidget(reset_button) main_layout.addLayout(reset_layout) # Add stretch at the bottom main_layout.addStretch() # Set up scroll area scroll_area.setWidget(content_widget) # Main window layout window_layout = QVBoxLayout(central_widget) window_layout.setContentsMargins(0, 0, 0, 0) window_layout.addWidget(scroll_area) # Apply global styling self.setStyleSheet(""" QMainWindow { background-color: #fafafa; } QScrollArea { background-color: #fafafa; border: none; } QWidget { background-color: #fafafa; } """) def create_setting_section(self, icon, title, description, has_toggle=False, has_file_input=False, browse_icon="📁"): # Main section widget section_widget = QWidget() section_widget.setStyleSheet(""" QWidget { background-color: white; border-radius: 8px; margin-bottom: 16px; } """) # Create main vertical layout for the section main_layout = QVBoxLayout(section_widget) main_layout.setContentsMargins(20, 20, 20, 20) main_layout.setSpacing(12) # Top row with icon, text, and toggle (if applicable) top_row_layout = QHBoxLayout() top_row_layout.setSpacing(16) # Icon - handle both emoji strings and image file paths icon_label = QLabel() icon_label.setFixedSize(48, 48) icon_label.setAlignment(Qt.AlignCenter) icon_label.setStyleSheet("background-color: transparent;") # Check if icon is a file path if isinstance(icon, str) and (icon.endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg')) or os.path.isfile(icon)): # Load image from file path pixmap = QPixmap(icon) if not pixmap.isNull(): # Scale the image to fit the icon size while maintaining aspect ratio scaled_pixmap = pixmap.scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation) icon_label.setPixmap(scaled_pixmap) else: # Fallback to default emoji if image fails to load icon_label.setText("📄") icon_label.setFont(QFont("Segoe UI", 16)) else: # Treat as emoji or text icon_label.setText(icon) icon_label.setFont(QFont("Segoe UI", 16)) top_row_layout.addWidget(icon_label) # Text content text_layout = QVBoxLayout() text_layout.setSpacing(4) title_label = QLabel(title) title_label.setFont(QFont("Segoe UI", 15, QFont.Medium)) title_label.setStyleSheet("color: #1a1a1a; background-color: transparent;") text_layout.addWidget(title_label) desc_label = QLabel(description) desc_label.setFont(QFont("Segoe UI", 13)) desc_label.setStyleSheet("color: #6b6b6b; background-color: transparent;") desc_label.setWordWrap(True) desc_label.setMinimumWidth(400) # Set minimum width for description text_layout.addWidget(desc_label) top_row_layout.addLayout(text_layout, 1) # Give text layout stretch factor of 1 top_row_layout.addStretch(0) # Reduce stretch to give more space to text # Add toggle switch if needed if has_toggle: self.toggle_switch = ModernToggleSwitch(self) top_row_layout.addWidget(self.toggle_switch) # Add the top row to main layout main_layout.addLayout(top_row_layout) # Add file input section if needed (under the description) if has_file_input: # File input layout file_input_layout = QHBoxLayout() file_input_layout.setSpacing(8) file_input_layout.setContentsMargins(40, 0, 0, 0) # Indent to align with text self.location_entry = QLineEdit() self.location_entry.setPlaceholderText("Select a file...") self.location_entry.setText("") self.location_entry.setStyleSheet(""" QLineEdit { background-color: #f8f8f8; border: 1px solid #e0e0e0; border-radius: 6px; padding: 8px 12px; font-size: 14px; color: #333; min-width: 300px; } QLineEdit:focus { border-color: #007acc; background-color: white; } """) file_input_layout.addWidget(self.location_entry) # Create browse button with custom icon browse_button_text = "Browse" if isinstance(browse_icon, str) and (browse_icon.endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg')) or os.path.isfile(browse_icon)): # Use image file for button icon self.browse_button = QPushButton(f" {browse_button_text}") # Load and set icon from file pixmap = QPixmap(browse_icon) if not pixmap.isNull(): # Scale icon for button (16x16 is good for buttons) scaled_pixmap = pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation) from PyQt5.QtGui import QIcon button_icon = QIcon(scaled_pixmap) self.browse_button.setIcon(button_icon) else: # Fallback to emoji if image fails to load self.browse_button.setText(f"📁 {browse_button_text}") else: # Use emoji or text icon self.browse_button = QPushButton(f"{browse_icon} {browse_button_text}") self.browse_button.clicked.connect(lambda: self.browse_file(icon_label)) self.browse_button.setStyleSheet(""" QPushButton { background-color: #f5f5f5; border: 1px solid #d1d1d1; border-radius: 6px; padding: 8px 16px; font-size: 14px; color: #333; font-weight: 500; } QPushButton:hover { background-color: #e8e8e8; border-color: #b8b8b8; } QPushButton:pressed { background-color: #d8d8d8; } """) file_input_layout.addWidget(self.browse_button) file_input_layout.addStretch() main_layout.addLayout(file_input_layout) # Adjust minimum height based on content if has_file_input: section_widget.setMinimumHeight(120) else: section_widget.setMinimumHeight(80) return section_widget def on_expose_toggle(self): """Handle expose toggle switch change""" self.expose_enabled = self.toggle_switch.checked def browse_file(self, icon_label=None): """Open file dialog to select a directory""" # Get current path current_path = self.location_entry.text() if self.location_entry.text() else "" # Open file dialog for directory selection directory = QFileDialog.getExistingDirectory( self, "Select Model Directory", current_path ) if directory: self.location_entry.setText(directory) def clear_inputs(self): """Reset all inputs to defaults""" self.location_entry.setText(self.args.models_dir) self.toggle_switch.checked = False self.toggle_switch.update() self.expose_enabled = False def main(): app = QApplication(sys.argv) # Set application properties app.setApplicationName("Settings") app.setApplicationVersion("1.0") # Create and show main window window = OllamaSettings() window.show() # Run the application sys.exit(app.exec_()) if __name__ == '__main__': main() ``` </details>
Author
Owner

@pdevine commented on GitHub (Jul 11, 2025):

There aren't any plans to add the settings UI for Linux currently. If we get enough requests we'll definitely add it.

<!-- gh-comment-id:3064300909 --> @pdevine commented on GitHub (Jul 11, 2025): There aren't any plans to add the settings UI for Linux currently. If we get enough requests we'll definitely add it.
Author
Owner

@olumolu commented on GitHub (Jul 12, 2025):

There aren't any plans to add the settings UI for Linux currently. If we get enough requests we'll definitely add it.

It is not making any sense remove settings from windows build and you will see no one will going to ask for that..
Most users are running them from mac or linux but if users don't know that something new already there how they can ask...
You should add this no matter what...
Feature parity is required.

<!-- gh-comment-id:3064378938 --> @olumolu commented on GitHub (Jul 12, 2025): > There aren't any plans to add the settings UI for Linux currently. If we get enough requests we'll definitely add it. It is not making any sense remove settings from windows build and you will see no one will going to ask for that.. Most users are running them from mac or linux but if users don't know that something new already there how they can ask... You should add this no matter what... Feature parity is required.
Author
Owner

@pdevine commented on GitHub (Jul 12, 2025):

Which settings are being removed? All settings are supported across all three platforms. The difference is we include the UI on Windows and Mac to make it easier. Linux users tend to have more experience and are more comfortable with using the command line and typically use Linux in headless environments.

<!-- gh-comment-id:3064436204 --> @pdevine commented on GitHub (Jul 12, 2025): Which settings are being removed? All settings are supported across all three platforms. The difference is we include the UI on Windows and Mac to make it easier. Linux users tend to have more experience and are more comfortable with using the command line and typically use Linux in headless environments.
Author
Owner

@rick-github commented on GitHub (Jul 12, 2025):

The python script emulates the look, feel and function of the Windows/macOS settings UI. It does require the user to auth as root when saving settings, or have configured sudoers.

<!-- gh-comment-id:3064450062 --> @rick-github commented on GitHub (Jul 12, 2025): The python script emulates the look, feel and function of the Windows/macOS settings UI. It does require the user to auth as root when saving settings, or have configured sudoers.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#33257