This commit is contained in:
2026-03-17 22:02:18 -07:00
parent ac41c90066
commit 15d31864db
5 changed files with 441 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
#!/bin/bash
HOME=/home/$USER
CURRENT="$(cat ~/.wallpaper)"
CURRENT="${CURRENT/\/\///}"
OUTPUT_DIR="/truenas/sudacode/pictures/wallpapers/"
cp "$CURRENT" "$HOME/Pictures/wallpapers/favorites/"
if cp "$CURRENT" "$OUTPUT_DIR"; then
notify-send "favorite-wallpaper" "Wallpaper saved to $OUTPUT_DIR"
else
notify-send "favorite-wallpaper" "Failed to saved wallpaper to $OUTPUT_DIR"
fi
# ft: sh

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
window_info=$(hyprctl activewindow -j)
read -r is_pinned window_class window_title <<< "$(echo "$window_info" | jq -r '[.pinned, .class, .title] | @tsv')"
hyprctl dispatch pin active
read -r window_x window_y window_w window_h <<< "$(echo "$window_info" | jq -r '[.at[0], .at[1], .size[0], .size[1]] | @tsv')"
screenshot=$(mktemp --suffix=.png)
grim -g "${window_x},${window_y} ${window_w}x${window_h}" "$screenshot"
if [ "$is_pinned" = "true" ]; then
status="Unpinned"
else
status="Pinned"
fi
notify-send -u low -i "$screenshot" "$status: $window_class" "$window_title"
rm -f "$screenshot"
# vim: set ft=sh

214
projects/scripts/popup-ai-chat.py Executable file
View File

@@ -0,0 +1,214 @@
#!/usr/bin/env python3
"""
Popup AI chat assistant using rofi for input and OpenRouter for responses.
"""
import os
import shutil
import subprocess
import sys
from typing import Optional
import requests
API_URL = "https://openrouter.ai/api/v1/chat/completions"
MODEL = os.environ.get("OPENROUTER_MODEL", "openai/gpt-oss-120b:free")
APP_NAME = "Popup AI Chat"
SYSTEM_PROMPT = (
"You are a helpful AI assistant. Give direct, accurate answers. "
"Use concise formatting unless the user asks for depth."
)
def load_api_key() -> str:
"""Load OpenRouter API key from env or fallback file."""
api_key = os.environ.get("OPENROUTER_API_KEY", "").strip()
if api_key:
return api_key
key_file = os.path.expanduser("~/.openrouterapikey")
if os.path.isfile(key_file):
with open(key_file, "r", encoding="utf-8") as handle:
return handle.read().strip()
return ""
def show_error(message: str) -> None:
"""Display an error message via zenity."""
subprocess.run(
["zenity", "--error", "--title", "Error", "--text", message],
stderr=subprocess.DEVNULL,
)
def check_dependencies() -> bool:
"""Validate required desktop tools are available."""
missing = [cmd for cmd in ("rofi", "zenity") if shutil.which(cmd) is None]
if not missing:
return True
message = f"Missing required command(s): {', '.join(missing)}"
if shutil.which("zenity") is not None:
show_error(message)
else:
print(f"Error: {message}", file=sys.stderr)
return False
def get_rofi_input() -> Optional[str]:
"""Ask for user input through rofi."""
result = subprocess.run(
["rofi", "-dmenu", "-i", "-p", "Ask AI"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
if result.returncode != 0:
return None
prompt = result.stdout.strip()
return prompt or None
def show_notification(body: str) -> None:
"""Show processing notification when notify-send exists."""
if shutil.which("notify-send") is None:
return
subprocess.Popen(
["notify-send", "-t", "0", "-a", APP_NAME, "Processing...", body],
stderr=subprocess.DEVNULL,
)
def close_notification() -> None:
"""Close the processing notification if one was sent."""
if shutil.which("pkill") is None:
return
subprocess.run(
["pkill", "-f", "notify-send.*Processing..."],
stderr=subprocess.DEVNULL,
)
def make_api_request(api_key: str, messages: list[dict[str, str]]) -> dict:
"""Send chat request to OpenRouter and return JSON payload."""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
"HTTP-Referer": "https://github.com/sudacode/scripts",
"X-Title": APP_NAME,
}
payload = {
"model": MODEL,
"messages": messages,
"temperature": 0.7,
}
response = requests.post(API_URL, headers=headers, json=payload, timeout=90)
return response.json()
def display_result(content: str) -> None:
"""Display model output in a text window."""
subprocess.run(
[
"zenity",
"--text-info",
"--title",
"AI Response",
"--width",
"900",
"--height",
"700",
"--font",
"monospace 11",
],
input=content,
text=True,
stderr=subprocess.DEVNULL,
)
def ask_follow_up() -> bool:
"""Ask if the user wants to continue the conversation."""
result = subprocess.run(
[
"zenity",
"--question",
"--title",
APP_NAME,
"--text",
"Ask a follow-up question?",
"--ok-label",
"Ask Follow-up",
"--cancel-label",
"Close",
],
stderr=subprocess.DEVNULL,
)
return result.returncode == 0
def extract_content(response: dict) -> str:
"""Extract assistant response from OpenRouter payload."""
if "error" in response:
message = response["error"].get("message", "Unknown API error")
raise ValueError(message)
try:
content = response["choices"][0]["message"]["content"]
except (KeyError, IndexError, TypeError) as exc:
raise ValueError("Failed to parse API response") from exc
if not content:
raise ValueError("Empty response from API")
return content
def main() -> int:
if not check_dependencies():
return 1
api_key = load_api_key()
if not api_key:
show_error("OPENROUTER_API_KEY environment variable is not set.")
return 1
history: list[dict[str, str]] = [{"role": "system", "content": SYSTEM_PROMPT}]
while True:
user_input = get_rofi_input()
if not user_input:
return 0
request_messages = history + [{"role": "user", "content": user_input}]
show_notification(f"Thinking: {user_input[:60]}...")
try:
response = make_api_request(api_key, request_messages)
content = extract_content(response)
except requests.RequestException as exc:
show_error(f"API request failed: {exc}")
return 1
except ValueError as exc:
show_error(str(exc))
return 1
except Exception as exc: # pragma: no cover
show_error(f"Unexpected error: {exc}")
return 1
finally:
close_notification()
history.append({"role": "user", "content": user_input})
history.append({"role": "assistant", "content": content})
display_result(content)
if not ask_follow_up():
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,181 @@
#!/bin/bash
# Japanese Learning Assistant using OpenRouter API
# Uses Google Gemini Flash 2.0 for AJATT-aligned Japanese analysis
# Configuration
OPENROUTER_API_KEY="${OPENROUTER_API_KEY:-}"
MODEL="${OPENROUTER_MODEL:-google/gemini-2.0-flash-001}"
API_URL="https://openrouter.ai/api/v1/chat/completions"
if [[ -z $OPENROUTER_API_KEY && -f "$HOME/.openrouterapikey" ]]; then
OPENROUTER_API_KEY="$(<"$HOME/.openrouterapikey")"
fi
# System prompt for Japanese learning
SYSTEM_PROMPT='You are my Japanese-learning assistant. Help me acquire Japanese through deep, AJATT-aligned analysis.
For every input, output exactly:
1. Japanese Input (Verbatim)
Repeat the original text exactly. Correct only critical OCR/punctuation errors.
2. Natural English Translation
Accurate and natural. Preserve tone, formality, and nuance. Avoid literalism.
3. Word-by-Word Breakdown
For each unit:
- Vocabulary: Part of speech + concise definition
- Grammar: Particles, conjugations, constructions (contextual usage)
- Nuance: Implied meaning, connotation, emotional tone, differences from similar expressions
Core Principles:
- Preserve native phrasing—never oversimplify
- Highlight subtle grammar, register shifts, and pragmatic implications
- Encourage pattern recognition; provide contrastive examples (e.g., ~のに vs ~けど)
- Focus on real Japanese usage
Rules:
- English explanations only (no romaji)
- Clean, structured formatting; calm, precise tone
- No filler text
Optional Additions (only when valuable):
- Synonyms, formality/register notes, cultural insights, common mistakes, extra native examples
Goal: Deep comprehension, natural grammar internalization, nuanced vocabulary, progress toward Japanese-only understanding.'
# Check for API key
if [[ -z "$OPENROUTER_API_KEY" ]]; then
zenity --error --text="OPENROUTER_API_KEY environment variable is not set." --title="Error" 2>/dev/null
exit 1
fi
# Get input from zenity
input=$(zenity --entry \
--title="Japanese Assistant" \
--text="Enter Japanese text to analyze:" \
--width=500 \
2>/dev/null)
# Exit if no input
if [[ -z "$input" ]]; then
exit 0
fi
# Show loading notification
notify-send -t 0 -a "Japanese Assistant" "Processing..." "Analyzing: ${input:0:50}..." &
notif_pid=$!
# Escape special characters for JSON
escape_json() {
local str="$1"
str="${str//\\/\\\\}"
str="${str//\"/\\\"}"
str="${str//$'\n'/\\n}"
str="${str//$'\r'/\\r}"
str="${str//$'\t'/\\t}"
printf '%s' "$str"
}
escaped_input=$(escape_json "$input")
escaped_system=$(escape_json "$SYSTEM_PROMPT")
# Build JSON payload
json_payload=$(
cat <<EOF
{
"model": "$MODEL",
"messages": [
{
"role": "system",
"content": "$escaped_system"
},
{
"role": "user",
"content": "$escaped_input"
}
],
"temperature": 0.7
}
EOF
)
# Make API request
response=$(curl -s -X POST "$API_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "HTTP-Referer: https://github.com/sudacode/scripts" \
-H "X-Title: Japanese Learning Assistant" \
-d "$json_payload")
# Close loading notification
pkill -f "notify-send.*Processing.*Analyzing" 2>/dev/null
# Check for errors
if [[ -z "$response" ]]; then
zenity --error --text="No response from API" --title="Error" 2>/dev/null
exit 1
fi
# Parse response and extract content using Python (handles Unicode properly)
result=$(echo "$response" | python3 -c "
import json
import sys
try:
data = json.load(sys.stdin)
if 'error' in data:
err = data['error'].get('message', 'Unknown error')
print(f'ERROR:{err}', end='')
sys.exit(1)
content = data.get('choices', [{}])[0].get('message', {}).get('content', '')
if not content:
print('ERROR:Failed to parse API response', end='')
sys.exit(1)
# Decode any unicode escape sequences in the content
try:
content = content.encode('utf-8').decode('unicode_escape').encode('latin-1').decode('utf-8')
except:
pass # Keep original if decoding fails
print(content, end='')
except json.JSONDecodeError as e:
print(f'ERROR:Invalid JSON response: {e}', end='')
sys.exit(1)
except Exception as e:
print(f'ERROR:{e}', end='')
sys.exit(1)
")
# Check for errors from Python parsing
if [[ "$result" == ERROR:* ]]; then
error_msg="${result#ERROR:}"
zenity --error --text="$error_msg" --title="Error" 2>/dev/null
exit 1
fi
content="$result"
if [[ -z "$content" ]]; then
zenity --error --text="Empty response from API" --title="Error" 2>/dev/null
exit 1
fi
# Display result in zenity
zenity --text-info \
--title="Japanese Analysis" \
--width=800 \
--height=600 \
--font="monospace" \
<<<"$content" 2>/dev/null

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
tmpfile=$(mktemp /tmp/screenshot-XXXXXX.png)
grim -g "$(hyprctl activewindow -j | jq -r '.at[0],.at[1],.size[0],.size[1]' | tr '\n' ' ' | awk '{print $1","$2" "$3"x"$4}')" "$tmpfile"
wl-copy < "$tmpfile"
notify-send -i "$tmpfile" "Screenshot of active window copied to clipboard"
rm -f "$tmpfile"