From ee9958966de7e7d98d421f0b14ae428c5df78b92 Mon Sep 17 00:00:00 2001 From: reetik-rana Date: Thu, 4 Jun 2026 22:59:27 +0530 Subject: [PATCH] Add standalone Wayland UI Python app --- entities.json | 19 +++++ wayland-ui-app/.gitignore | 7 ++ wayland-ui-app/LICENCE | 21 +++++ wayland-ui-app/README.md | 73 ++++++++++++++++ wayland-ui-app/setup_environment.sh | 61 +++++++++++++ wayland-ui-app/stringcast_ui.py | 124 +++++++++++++++++++++++++++ wayland-ui-app/system_enhancer.py | 128 ++++++++++++++++++++++++++++ 7 files changed, 433 insertions(+) create mode 100644 entities.json create mode 100644 wayland-ui-app/.gitignore create mode 100644 wayland-ui-app/LICENCE create mode 100644 wayland-ui-app/README.md create mode 100755 wayland-ui-app/setup_environment.sh create mode 100644 wayland-ui-app/stringcast_ui.py create mode 100644 wayland-ui-app/system_enhancer.py diff --git a/entities.json b/entities.json new file mode 100644 index 0000000..f1f2b2b --- /dev/null +++ b/entities.json @@ -0,0 +1,19 @@ +{ + "people": [ + "Abhinav Gautam" + ], + "projects": [ + "stringcast" + ], + "topics": [ + "Rust", + "Python", + "Linux", + "Windows", + "Wayland", + "Gemini", + "OpenAI", + "Goals", + "Provider" + ] +} \ No newline at end of file diff --git a/wayland-ui-app/.gitignore b/wayland-ui-app/.gitignore new file mode 100644 index 0000000..9c8ab67 --- /dev/null +++ b/wayland-ui-app/.gitignore @@ -0,0 +1,7 @@ + +.venv +venv +build +dist +.env +__pycache__ \ No newline at end of file diff --git a/wayland-ui-app/LICENCE b/wayland-ui-app/LICENCE new file mode 100644 index 0000000..19ac5ec --- /dev/null +++ b/wayland-ui-app/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2026] [mclovin22117] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/wayland-ui-app/README.md b/wayland-ui-app/README.md new file mode 100644 index 0000000..c376315 --- /dev/null +++ b/wayland-ui-app/README.md @@ -0,0 +1,73 @@ +# Stringcast (Wayland UI Edition) + +## Introduction + +Stringcast is a lightweight, floating desktop utility that uses AI to instantly transform your text. + +Whether you need to fix grammar, adopt a professional tone, or translate a paragraph, Stringcast acts as your persistent, "always-on-top" AI assistant right on your desktop. + +This project is specifically designed as a secure, root-free solution for modern Linux desktops running Wayland. + +## πŸ›‘οΈ Why a UI App instead of a Keystroke Listener? + +If you are using a modern Linux desktop environment (like GNOME or KDE on Wayland), you might wonder why this isn't a background script that listens to your keyboard. + +### The Wayland Security Sandbox: By design, Wayland strictly isolates applications. It intentionally blocks global key-logging and synthetic keystroke injection to protect users from malicious software. + +### The Root Privilege Problem: While workarounds exist (like reading raw hardware events via /dev/input), they absolutely require running scripts as root (using sudo). + +### The Environment Conflict: Running desktop scripts as root is highly insecure, cumbersome for daily use, and breaks standard Python virtual environments. + +### The UI Solution: This application uses a native PyQt6 interface configured to hover "always on top" of your other windows. It provides a frictionless workflow that fully respects Wayland's security sandboxβ€”meaning no sudo is required! + +## πŸš€ Installation & Usage (For Users) + +You do not need to install Python, run pip, or touch terminal commands to use Stringcast. + +### Download the App: Go to the Releases page of this repository and download the latest standalone executable (e.g., stringcast_ui or system_enhancer). + +### Setup your API Key: In the exact same folder where you downloaded the app, create a new text file named .env. Open it and add your Gemini API key like this: +GEMINI_API_KEY="your_actual_key_here" + +### Make it Executable: Linux requires you to give downloaded apps permission to run. Right-click the downloaded file -> Properties -> Permissions -> check "Allow executing file as program". (Alternatively, run chmod +x filename in your terminal). + +### Run Stringcast: Double-click the file! Type or paste your text into the floating window, select a transformation mode from the dropdown, and hit "Transform Text". + +## πŸ› οΈ Cloning & Tweaking (For Developers) + +Want to add your own custom AI prompts (like "Rewrite as a Pirate" or "Translate to Python code")? It is incredibly easy to modify. + +### Clone the repository: +git clone https://github.com/mclovin22117/stringcast-wayland.git +cd stringcast-wayland + +### Set up a virtual environment: +python3 -m venv venv +source venv/bin/activate + +### Install dependencies: +pip install PyQt6 google-generativeai openai python-dotenv pyinstaller + +### Make your changes: +Open your main Python UI script and simply modify the dropdown list items +(e.g., self.option_dropdown.addItems([...])) to add new features. + The backend AI automatically understands natural language commands! + +### Rebuild the standalone app: +pyinstaller --noconsole --onefile your_script_name.py + +Your new custom app will be compiled and waiting for you inside the dist/ folder. + +## 🀝 Contributing + +Contributions are always welcome! If you have an idea to improve the UI, add new AI providers, or optimize the codebase: + +Fork the Project + +Create your Feature Branch (git checkout -b feature/AmazingFeature) + +Commit your Changes (git commit -m 'Add some AmazingFeature') + +Push to the Branch (git push origin feature/AmazingFeature) + +Open a Pull Request \ No newline at end of file diff --git a/wayland-ui-app/setup_environment.sh b/wayland-ui-app/setup_environment.sh new file mode 100755 index 0000000..78d3fcb --- /dev/null +++ b/wayland-ui-app/setup_environment.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# =============================================== +# AI Text Enhancer Setup and Launcher +# Instructions: +# 1. Make sure you have python3 installed (sudo apt install python3) +# 2. Place this script in the same folder as 'system_enhancer.py' +# 3. Run: ./setup_environment.sh +# ================================================= + +# Exit immediately if any command fails +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +PYTHON_SCRIPT="${SCRIPT_DIR}/system_enhancer.py" + +echo "===================================================" +echo " AI Text Enhancer Setup and Launcher" +echo "=========================================================" + +# --- Dependency Check --- +if ! command -v python3 &> /dev/null; then + echo "❌ ERROR: python3 command not found." + echo "Please install Python 3 first (e.g., sudo apt update && sudo apt install python3)." + exit 1 +fi + +# --- 1. Virtual Environment Setup --- +VENV_DIR=".venv" +if [ ! -d "$VENV_DIR" ]; then + echo "βœ… Creating isolated Python virtual environment in '$VENV_DIR'..." + python3 -m venv "$VENV_DIR" +else + echo "βœ… Virtual environment already exists at '$VENV_DIR'. Skipping creation." +fi + +# --- 2. Dependency Installation --- +echo "Activating virtual environment..." +source "$VENV_DIR/bin/activate" + +# *** FIX APPLIED HERE: Installing the missing SDK *** +echo "Installing required Python packages (including google-generativeai)..." +pip install google-generativeai +# *** FIX APPLIED HERE: Installing the missing SDKs *** +echo "Installing required Python packages..." +pip install google-generativeai openai python-dotenv +# Add any other packages your code uses (e.g., pip install requests) + +# --- 3. Execution --- +echo -e "\n============================================================" +echo "Setup Complete. Running the application now..." +echo "============================================================" + +# Execute the main script using the activated environment's Python interpreter +python "$PYTHON_SCRIPT" + +# --- 4. Cleanup --- +echo -e "\n==================================================" +echo "Application finished." +deactivate +echo "==========================================================" \ No newline at end of file diff --git a/wayland-ui-app/stringcast_ui.py b/wayland-ui-app/stringcast_ui.py new file mode 100644 index 0000000..3cdd45d --- /dev/null +++ b/wayland-ui-app/stringcast_ui.py @@ -0,0 +1,124 @@ +import sys +from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, + QTextEdit, QPushButton, QComboBox, QLabel) +from PyQt6.QtCore import Qt, QThread, pyqtSignal +from PyQt6.QtGui import QGuiApplication + +# Import your working AI engine +from system_enhancer import try_transform_text + +class AIWorker(QThread): + """Runs the API call in the background so the UI doesn't freeze.""" + finished = pyqtSignal(str) + + def __init__(self, text, function_name): + super().__init__() + self.text = text + self.function_name = function_name + + def run(self): + try: + result = try_transform_text(self.text, self.function_name) + self.finished.emit(result) + except Exception as e: + self.finished.emit(f"Error: {str(e)}") + +class StringcastApp(QWidget): + def __init__(self): + super().__init__() + self.initUI() + + def initUI(self): + # --- Window Settings --- + self.setWindowTitle('Stringcast - Hover Mode') + self.resize(400, 500) + + # Make the window float on top of all other applications + self.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint) + + # --- Layout & Widgets --- + layout = QVBoxLayout() + + # Input Area + self.input_label = QLabel("Input Text:") + self.input_box = QTextEdit() + self.input_box.setPlaceholderText("Type or paste your text here...") + + # Options + self.option_dropdown = QComboBox() + self.option_dropdown.addItems([ + "Improve Grammar & Flow", + "Enhance Tone to Professional", + "Make it More Engaging", + "Translate to English", + "Translate to Hindi", + "Enoji based on this text", + "Translate to Spanish", + "Translate to French", + "Summarize concisely" + ]) + + # Transform Button + self.transform_btn = QPushButton("✨ Transform Text") + self.transform_btn.setStyleSheet("background-color: #2b5797; color: white; font-weight: bold; padding: 10px;") + self.transform_btn.clicked.connect(self.run_transformation) + + # Output Area + self.output_label = QLabel("Enhanced Output:") + self.output_box = QTextEdit() + self.output_box.setReadOnly(True) + self.output_box.setStyleSheet("background-color: #f4f4f4; color: #000000;") + + # Copy Button + self.copy_btn = QPushButton("πŸ“‹ Copy to Clipboard") + self.copy_btn.clicked.connect(self.copy_to_clipboard) + + # Assemble Layout + layout.addWidget(self.input_label) + layout.addWidget(self.input_box) + layout.addWidget(self.option_dropdown) + layout.addWidget(self.transform_btn) + layout.addWidget(self.output_label) + layout.addWidget(self.output_box) + layout.addWidget(self.copy_btn) + + self.setLayout(layout) + + def run_transformation(self): + text = self.input_box.toPlainText().strip() + if not text: + return + + mode = self.option_dropdown.currentText() + + # Update UI state + self.transform_btn.setText("⏳ Processing...") + self.transform_btn.setEnabled(False) + self.output_box.setText("") + + # Run AI in background thread + self.worker = AIWorker(text, mode) + self.worker.finished.connect(self.on_transformation_complete) + self.worker.start() + + def on_transformation_complete(self, result): + # Restore UI state and show result + self.output_box.setText(result) + self.transform_btn.setText("✨ Transform Text") + self.transform_btn.setEnabled(True) + + def copy_to_clipboard(self): + clipboard = QGuiApplication.clipboard() + clipboard.setText(self.output_box.toPlainText()) + self.copy_btn.setText("βœ… Copied!") + + # Reset button text after 2 seconds + from PyQt6.QtCore import QTimer + QTimer.singleShot(2000, lambda: self.copy_btn.setText("πŸ“‹ Copy to Clipboard")) + +if __name__ == '__main__': + app = QApplication(sys.argv) + app.setStyle('Fusion') # Clean, modern default look + ex = StringcastApp() + ex.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/wayland-ui-app/system_enhancer.py b/wayland-ui-app/system_enhancer.py new file mode 100644 index 0000000..166e5bf --- /dev/null +++ b/wayland-ui-app/system_enhancer.py @@ -0,0 +1,128 @@ +import os +import sys +from dotenv import load_dotenv + +# Load variables from the .env file into the environment +load_dotenv() + +try: + import google.generativeai as genai +except ImportError: + print("Warning: gemini library not found. Install with: pip install google-generativeai") + +try: + from openai import OpenAI +except ImportError: + print("Warning: openai library not found. Install with: pip install openai") + +# --- Configuration --- +DEFAULT_KEYS = { + "Gemini": os.environ.get("GEMINI_API_KEY"), + "OpenAI": os.environ.get("OPENAI_API_KEY"), +} +PROVIDER_ORDER = ["Gemini", "OpenAI"] + +# --- API Client Setup --- +def initialize_clients(): + """Initializes API clients based on environment variables.""" + clients = {} + + gemini_key = DEFAULT_KEYS["Gemini"] + if gemini_key: + try: + genai.configure(api_key=gemini_key) + # Store the model identifier we want to use + clients["Gemini"] = "gemini-2.5-flash" + print("βœ… Client initialized for Gemini.") + except Exception as e: + print(f"❌ Error initializing Gemini client: {e}") + + openai_key = DEFAULT_KEYS["OpenAI"] + if openai_key: + try: + clients["OpenAI"] = OpenAI(api_key=openai_key) + print("βœ… Client initialized for OpenAI.") + except Exception as e: + print(f"❌ Error initializing OpenAI client: {e}") + + return clients + +AI_CLIENTS = initialize_clients() + +def construct_prompt(function_name: str) -> str: + """Builds the system instruction based on the selected enhancement task.""" + print(f" [Prompting] Building context for: {function_name}...") + if function_name == "Improve Grammar & Flow": + return "You are a master copyeditor. Improve the grammar, sentence structure, and overall flow of the following text while strictly maintaining the original meaning. Return ONLY the perfected text, without any conversational preamble." + elif function_name == "Enhance Tone to Professional": + return "Rewrite the following text to adopt a formal, highly professional, and measured tone suitable for a corporate memo. Do not alter the core message. Return ONLY the rewritten text." + elif function_name.startswith("Translate"): + target_lang = function_name.split("to ")[-1].strip() + return f"Translate the following text accurately and naturally into {target_lang}. Return ONLY the translated text." + else: + return "Please process this text with maximum clarity and sophistication. Return ONLY the processed text." + +def call_llm_api(provider_name: str, text_to_enhance: str, system_instruction: str) -> str: + """CORE API INTERACTION FUNCTION.""" + print(f" -> Sending request to {provider_name}...") + + if provider_name == "Gemini": + model_name = AI_CLIENTS.get("Gemini") + if not model_name: + raise ValueError("Gemini client not initialized.") + + # Gemini handles system instructions via the GenerativeModel constructor or by prepending + model = genai.GenerativeModel(model_name) + full_prompt = f"{system_instruction}\n\nText to process:\n{text_to_enhance}" + response = model.generate_content(full_prompt) + return response.text.strip() + + elif provider_name == "OpenAI": + client = AI_CLIENTS.get("OpenAI") + if not client: + raise ValueError("OpenAI client not initialized.") + + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": system_instruction}, + {"role": "user", "content": text_to_enhance} + ] + ) + return response.choices[0].message.content.strip() + + raise NotImplementedError(f"API call logic for {provider_name} is not implemented.") + +def try_transform_text(text_to_enhance: str, function_name: str) -> str: + """Attempts to transform text using multiple APIs in sequence.""" + system_instruction = construct_prompt(function_name) + + for provider_name in PROVIDER_ORDER: + if provider_name not in AI_CLIENTS: + print(f"⚠️ Skipping {provider_name} (Not configured or missing API key).") + continue + + print(f"\n--- Attempting connection using {provider_name} ---") + try: + result = call_llm_api(provider_name, text_to_enhance, system_instruction) + print(f"βœ… Success using {provider_name}!") + return result + except Exception as e: + print(f"❌ Failed using {provider_name}: {e.__class__.__name__} - {e}. Attempting fallback...") + continue + + return "\n⚠️ FATAL: ALL configured APIs failed to process the request." + +# ==================================================================== +# REAL EXECUTION EXAMPLES +# ==================================================================== +if __name__ == "__main__": + print("\n=========================================") + print("RUNNING REAL TEST: Improve Grammar") + print("=========================================") + sample_text = "i dont knwo whats happening but the code is crash." + + result = try_transform_text(sample_text, "Improve Grammar & Flow") + + print("\n[ORIGINAL]:", sample_text) + print("[ENHANCED]:", result) \ No newline at end of file