mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-01-15 01:20:44 -08:00
update
This commit is contained in:
@@ -93,9 +93,11 @@ bindl = , XF86AudioPrev, exec, mpc prev
|
|||||||
# rofi
|
# rofi
|
||||||
bind = $mainMod SHIFT, v, exec, uwsm app -sb -- rofi-rbw
|
bind = $mainMod SHIFT, v, exec, uwsm app -sb -- rofi-rbw
|
||||||
bind = $mainMod, w, exec, rofi -show window -theme $HOME/.config/rofi/launchers/type-2/style-2.rasi -dpi 96 -theme-str 'window {width: 35%;}'
|
bind = $mainMod, w, exec, rofi -show window -theme $HOME/.config/rofi/launchers/type-2/style-2.rasi -dpi 96 -theme-str 'window {width: 35%;}'
|
||||||
bind = $mainMod SHIFT, w, exec, $HOME/.config/rofi/scripts/rofi-wallpaper.sh
|
bind = $mainMod SHIFT, w, exec, "$HOME/.config/rofi/scripts/rofi-wallpaper.sh"
|
||||||
bind = $mainMod SHIFT, d, exec, $HOME/.config/rofi/scripts/rofi-docs.sh
|
bind = $mainMod SHIFT, d, exec, "$HOME/.config/rofi/scripts/rofi-docs.sh"
|
||||||
bind = SUPER, j, exec, "$HOME/.config/rofi/scripts/rofi-jellyfin-dir.sh"
|
bind = SUPER, j, exec, "$HOME/.config/rofi/scripts/rofi-jellyfin-dir.sh"
|
||||||
|
bind = $mainMod SHIFT, t, exec, "$HOME/.config/rofi/scripts/rofi-launch-texthooker-steam.sh"
|
||||||
|
bind = $mainMod, t, exec, "$HOME/projects/scripts/popup-ai-translator.py"
|
||||||
|
|
||||||
# ncmcppp
|
# ncmcppp
|
||||||
bind = $mainMod, n, exec, uwsm app -sb -- ghostty --command=/usr/bin/ncmpcpp
|
bind = $mainMod, n, exec, uwsm app -sb -- ghostty --command=/usr/bin/ncmpcpp
|
||||||
|
|||||||
248
projects/scripts/popup-ai-translator.py
Executable file
248
projects/scripts/popup-ai-translator.py
Executable file
@@ -0,0 +1,248 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Japanese Learning Assistant using OpenRouter API
|
||||||
|
Uses Google Gemini Flash 2.0 for AJATT-aligned Japanese analysis
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
|
||||||
|
MODEL = os.environ.get("OPENROUTER_MODEL", "google/gemini-2.0-flash-001")
|
||||||
|
API_URL = "https://openrouter.ai/api/v1/chat/completions"
|
||||||
|
|
||||||
|
# Try to load API key from file if not in environment
|
||||||
|
if not OPENROUTER_API_KEY:
|
||||||
|
key_file = os.path.expanduser("~/.openrouterapikey")
|
||||||
|
if os.path.isfile(key_file):
|
||||||
|
with open(key_file, "r") as f:
|
||||||
|
OPENROUTER_API_KEY = f.read().strip()
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = """You are my Japanese-learning assistant. Help me acquire Japanese through deep, AJATT-aligned analysis.
|
||||||
|
|
||||||
|
For every input, output exactly using clean plain text formatting:
|
||||||
|
|
||||||
|
═══════════════════════════════════════
|
||||||
|
1. JAPANESE INPUT
|
||||||
|
═══════════════════════════════════════
|
||||||
|
|
||||||
|
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. BREAKDOWN
|
||||||
|
═══════════════════════════════════════
|
||||||
|
|
||||||
|
For each token, provide a breakdown of the vocabulary, grammar, and nuance.
|
||||||
|
|
||||||
|
▸ 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)
|
||||||
|
• Use the section dividers shown above (═══) for major sections
|
||||||
|
• Use ▸ for sub-items and • for bullet points
|
||||||
|
• Put Japanese terms in 「brackets」
|
||||||
|
• 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."""
|
||||||
|
|
||||||
|
|
||||||
|
def show_error(message: str) -> None:
|
||||||
|
"""Display an error dialog using zenity."""
|
||||||
|
subprocess.run(
|
||||||
|
["zenity", "--error", "--text", message, "--title", "Error"],
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_clipboard() -> str:
|
||||||
|
"""Get clipboard contents using wl-paste or xclip."""
|
||||||
|
# Try wl-paste first (Wayland)
|
||||||
|
result = subprocess.run(
|
||||||
|
["wl-paste", "--no-newline"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
# Fall back to xclip (X11)
|
||||||
|
result = subprocess.run(
|
||||||
|
["xclip", "-selection", "clipboard", "-o"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_input() -> str | None:
|
||||||
|
"""Get input from user via zenity entry dialog, pre-populated with clipboard."""
|
||||||
|
clipboard = get_clipboard()
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"zenity",
|
||||||
|
"--entry",
|
||||||
|
"--title",
|
||||||
|
"Japanese Assistant",
|
||||||
|
"--text",
|
||||||
|
"Enter Japanese text to analyze (press Enter to send):",
|
||||||
|
"--width",
|
||||||
|
"600",
|
||||||
|
]
|
||||||
|
|
||||||
|
if clipboard:
|
||||||
|
cmd.extend(["--entry-text", clipboard])
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return None
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def show_notification(message: str, body: str) -> subprocess.Popen:
|
||||||
|
"""Show a notification using notify-send."""
|
||||||
|
return subprocess.Popen(
|
||||||
|
["notify-send", "-t", "0", "-a", "Japanese Assistant", message, body],
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def close_notification() -> None:
|
||||||
|
"""Close the processing notification."""
|
||||||
|
subprocess.run(
|
||||||
|
["pkill", "-f", "notify-send.*Processing.*Analyzing"],
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def display_result(content: str) -> None:
|
||||||
|
"""Display the result in a zenity text-info dialog."""
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"zenity",
|
||||||
|
"--text-info",
|
||||||
|
"--title",
|
||||||
|
"Japanese Analysis",
|
||||||
|
"--width",
|
||||||
|
"900",
|
||||||
|
"--height",
|
||||||
|
"700",
|
||||||
|
"--font",
|
||||||
|
"monospace 11",
|
||||||
|
],
|
||||||
|
input=content,
|
||||||
|
text=True,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_api_request(user_input: str) -> dict:
|
||||||
|
"""Make the API request to OpenRouter."""
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
|
||||||
|
"HTTP-Referer": "https://github.com/sudacode/scripts",
|
||||||
|
"X-Title": "Japanese Learning Assistant",
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"model": MODEL,
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": SYSTEM_PROMPT},
|
||||||
|
{"role": "user", "content": user_input},
|
||||||
|
],
|
||||||
|
"temperature": 0.7,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(API_URL, headers=headers, json=payload, timeout=60)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
# Check for API key
|
||||||
|
if not OPENROUTER_API_KEY:
|
||||||
|
show_error("OPENROUTER_API_KEY environment variable is not set.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Get input from user
|
||||||
|
user_input = get_input()
|
||||||
|
if not user_input:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Show loading notification
|
||||||
|
show_notification("Processing...", f"Analyzing: {user_input[:50]}...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Make API request
|
||||||
|
response = make_api_request(user_input)
|
||||||
|
|
||||||
|
# Close loading notification
|
||||||
|
close_notification()
|
||||||
|
|
||||||
|
# Check for errors in response
|
||||||
|
if "error" in response:
|
||||||
|
error_msg = response["error"].get("message", "Unknown error")
|
||||||
|
show_error(error_msg)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Extract content from response
|
||||||
|
try:
|
||||||
|
content = response["choices"][0]["message"]["content"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
show_error("Failed to parse API response")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not content:
|
||||||
|
show_error("Empty response from API")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Display result
|
||||||
|
display_result(content)
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
close_notification()
|
||||||
|
show_error(f"API request failed: {e}")
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
close_notification()
|
||||||
|
show_error(f"Error: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user