socratic-debug 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- socratic.py +8 -0
- socratic_debug-0.0.1.dist-info/METADATA +143 -0
- socratic_debug-0.0.1.dist-info/RECORD +40 -0
- socratic_debug-0.0.1.dist-info/WHEEL +5 -0
- socratic_debug-0.0.1.dist-info/entry_points.txt +2 -0
- socratic_debug-0.0.1.dist-info/licenses/LICENSE +21 -0
- socratic_debug-0.0.1.dist-info/top_level.txt +3 -0
- src/__init__.py +1 -0
- src/agent/graph/graph.py +47 -0
- src/agent/llm/factory.py +15 -0
- src/agent/llm/providers/__init__.py +8 -0
- src/agent/llm/providers/ollama.py +13 -0
- src/agent/llm/providers/openrouter.py +14 -0
- src/agent/nodes/diagnose_node.py +21 -0
- src/agent/nodes/evaluate_node.py +38 -0
- src/agent/nodes/human_input_node.py +18 -0
- src/agent/nodes/questioner_node.py +47 -0
- src/agent/nodes/solution_node.py +36 -0
- src/agent/prompts/diagnose_prompt.py +34 -0
- src/agent/prompts/evaluation_prompt.py +33 -0
- src/agent/prompts/mentor_prompt.py +30 -0
- src/agent/prompts/questioner_prompt.py +47 -0
- src/agent/prompts/solution_prompt.py +38 -0
- src/agent/runner.py +21 -0
- src/agent/state/state.py +21 -0
- src/cli.py +98 -0
- src/config_creator.py +32 -0
- src/config_template.py +21 -0
- src/extractor.py +34 -0
- src/hook.py +67 -0
- src/utils/read_yaml.py +36 -0
- ui/__init__.py +15 -0
- ui/app.py +203 -0
- ui/bidi.py +23 -0
- ui/console.py +23 -0
- ui/styles.py +63 -0
- ui/templates.py +102 -0
- ui/translations.py +31 -0
- ui/views.py +52 -0
- ui/window.py +140 -0
ui/styles.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Global QSS Theme for Socratic Terminal GUI
|
|
2
|
+
SOCRATIC_DARK_THEME = """
|
|
3
|
+
QMainWindow {
|
|
4
|
+
background-color: #0d1117;
|
|
5
|
+
}
|
|
6
|
+
QWidget {
|
|
7
|
+
color: #e6edf3;
|
|
8
|
+
font-family: 'Segoe UI', 'Ubuntu', 'DejaVu Sans', 'Noto Sans Arabic', sans-serif;
|
|
9
|
+
font-size: 14px;
|
|
10
|
+
}
|
|
11
|
+
QFrame#crashBanner {
|
|
12
|
+
background-color: #1f1315;
|
|
13
|
+
border: 1px solid #da3633;
|
|
14
|
+
border-radius: 8px;
|
|
15
|
+
padding: 10px;
|
|
16
|
+
}
|
|
17
|
+
QTextEdit#chatArea {
|
|
18
|
+
background-color: #161b22;
|
|
19
|
+
border: 1px solid #30363d;
|
|
20
|
+
border-radius: 8px;
|
|
21
|
+
padding: 12px;
|
|
22
|
+
line-height: 1.6;
|
|
23
|
+
}
|
|
24
|
+
QLineEdit#inputField {
|
|
25
|
+
background-color: #161b22;
|
|
26
|
+
border: 1px solid #388bfd;
|
|
27
|
+
border-radius: 6px;
|
|
28
|
+
padding: 10px 14px;
|
|
29
|
+
font-size: 14px;
|
|
30
|
+
color: #ffffff;
|
|
31
|
+
}
|
|
32
|
+
QLineEdit#inputField:focus {
|
|
33
|
+
border: 2px solid #58a6ff;
|
|
34
|
+
background-color: #1c2128;
|
|
35
|
+
}
|
|
36
|
+
QPushButton#sendButton {
|
|
37
|
+
background-color: #238636;
|
|
38
|
+
color: #ffffff;
|
|
39
|
+
font-weight: bold;
|
|
40
|
+
border: none;
|
|
41
|
+
border-radius: 6px;
|
|
42
|
+
padding: 10px 24px;
|
|
43
|
+
font-size: 14px;
|
|
44
|
+
}
|
|
45
|
+
QPushButton#sendButton:hover {
|
|
46
|
+
background-color: #2ea043;
|
|
47
|
+
}
|
|
48
|
+
QPushButton#sendButton:disabled {
|
|
49
|
+
background-color: #21262d;
|
|
50
|
+
color: #8b949e;
|
|
51
|
+
}
|
|
52
|
+
QLabel#headerTitle {
|
|
53
|
+
font-size: 16px;
|
|
54
|
+
font-weight: bold;
|
|
55
|
+
color: #58a6ff;
|
|
56
|
+
}
|
|
57
|
+
QLabel#statusBadge {
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
font-weight: bold;
|
|
60
|
+
padding: 4px 10px;
|
|
61
|
+
border-radius: 4px;
|
|
62
|
+
}
|
|
63
|
+
"""
|
ui/templates.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ChatTemplates:
|
|
6
|
+
"""HTML templates for rendering styled chat bubbles in QTextEdit."""
|
|
7
|
+
|
|
8
|
+
@staticmethod
|
|
9
|
+
def format_markdown(text: str) -> str:
|
|
10
|
+
"""Converts basic markdown code blocks and inline formatting to HTML."""
|
|
11
|
+
parts = []
|
|
12
|
+
lines = text.split("\n")
|
|
13
|
+
in_code = False
|
|
14
|
+
code_buffer = []
|
|
15
|
+
|
|
16
|
+
for line in lines:
|
|
17
|
+
if line.strip().startswith("```"):
|
|
18
|
+
if in_code:
|
|
19
|
+
code_content = html.escape("\n".join(code_buffer))
|
|
20
|
+
parts.append(
|
|
21
|
+
f"<pre style='background: #0d1117; color: #58a6ff; padding: 10px; border-radius: 6px; font-family: monospace; font-size: 13px; text-align: left; direction: ltr;'>{code_content}</pre>"
|
|
22
|
+
)
|
|
23
|
+
code_buffer = []
|
|
24
|
+
in_code = False
|
|
25
|
+
else:
|
|
26
|
+
in_code = True
|
|
27
|
+
continue
|
|
28
|
+
|
|
29
|
+
if in_code:
|
|
30
|
+
code_buffer.append(line)
|
|
31
|
+
else:
|
|
32
|
+
escaped = html.escape(line)
|
|
33
|
+
escaped = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', escaped)
|
|
34
|
+
escaped = re.sub(
|
|
35
|
+
r'`(.*?)`',
|
|
36
|
+
r"<code style='background: #21262d; color: #79c0ff; padding: 2px 5px; border-radius: 4px; font-family: monospace; direction: ltr;'>\1</code>",
|
|
37
|
+
escaped,
|
|
38
|
+
)
|
|
39
|
+
parts.append(escaped + "<br/>")
|
|
40
|
+
|
|
41
|
+
if in_code and code_buffer:
|
|
42
|
+
code_content = html.escape("\n".join(code_buffer))
|
|
43
|
+
parts.append(
|
|
44
|
+
f"<pre style='background: #0d1117; color: #58a6ff; padding: 10px; border-radius: 6px; font-family: monospace; font-size: 13px; text-align: left; direction: ltr;'>{code_content}</pre>"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return "".join(parts)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def mentor_bubble(content: str, hint_level: int, mentor_name: str, hint_label: str, is_arabic: bool) -> str:
|
|
51
|
+
border_side = "border-right" if is_arabic else "border-left"
|
|
52
|
+
formatted_html = ChatTemplates.format_markdown(content)
|
|
53
|
+
return f"""
|
|
54
|
+
<div style='margin-bottom: 16px; background-color: #111a2e; {border_side}: 4px solid #388bfd; border-radius: 6px; padding: 12px;'>
|
|
55
|
+
<div style='color: #58a6ff; font-weight: bold; font-size: 13px; margin-bottom: 6px;'>
|
|
56
|
+
🤖 {mentor_name} <span style='color: #8b949e; font-size: 11px;'>({hint_label} {hint_level}/3)</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div style='color: #f0f6fc; font-size: 14px; line-height: 1.6;'>
|
|
59
|
+
{formatted_html}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def user_bubble(text: str, user_tag: str, is_arabic: bool) -> str:
|
|
66
|
+
border_side = "border-left" if is_arabic else "border-right"
|
|
67
|
+
escaped_text = html.escape(text)
|
|
68
|
+
return f"""
|
|
69
|
+
<div style='margin-bottom: 16px; background-color: #1b2128; {border_side}: 4px solid #f2cc60; border-radius: 6px; padding: 10px 14px;'>
|
|
70
|
+
<div style='color: #f2cc60; font-weight: bold; font-size: 12px; margin-bottom: 4px;'>
|
|
71
|
+
👤 {user_tag}
|
|
72
|
+
</div>
|
|
73
|
+
<div style='color: #ffffff; font-size: 14px;'>
|
|
74
|
+
{escaped_text}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def verdict_badge(verdict: str) -> str:
|
|
81
|
+
color = "#3fb950" if "SOLVED" in verdict else ("#f85149" if "SURRENDER" in verdict else "#d29922")
|
|
82
|
+
return f"""
|
|
83
|
+
<div style='text-align: center; margin: 8px 0;'>
|
|
84
|
+
<span style='background-color: #161b22; color: {color}; border: 1px solid {color}; border-radius: 12px; padding: 3px 14px; font-size: 12px; font-weight: bold;'>
|
|
85
|
+
{verdict}
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def solution_bubble(content: str, wrapup_title: str) -> str:
|
|
92
|
+
formatted_html = ChatTemplates.format_markdown(content)
|
|
93
|
+
return f"""
|
|
94
|
+
<div style='margin-top: 14px; margin-bottom: 20px; background-color: #122119; border: 2px solid #238636; border-radius: 8px; padding: 14px;'>
|
|
95
|
+
<div style='color: #3fb950; font-weight: bold; font-size: 15px; margin-bottom: 8px;'>
|
|
96
|
+
{wrapup_title}
|
|
97
|
+
</div>
|
|
98
|
+
<div style='color: #e6edf3; font-size: 14px; line-height: 1.6;'>
|
|
99
|
+
{formatted_html}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
"""
|
ui/translations.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Multilingual Dictionary for Socratic UI
|
|
2
|
+
TRANSLATIONS = {
|
|
3
|
+
"arabic": {
|
|
4
|
+
"app_title": "🧠 سُقراط ديبَج — المُرشد البرمجي التفاعلي",
|
|
5
|
+
"header_title": "🧠 المُرشد السقراطي للبرمجة",
|
|
6
|
+
"status_active": "● الجلسة نشطة",
|
|
7
|
+
"status_solved": "✔ تم الحل بنجاح",
|
|
8
|
+
"crash_location": "الموقع",
|
|
9
|
+
"crashing_line": "السطر المسبب للخطأ",
|
|
10
|
+
"placeholder": "اكتب إجابتك هنا، أو اكتب 'خروج' للإنهاء...",
|
|
11
|
+
"send_btn": "إرسال",
|
|
12
|
+
"mentor_tag": "المُرشد السقراطي",
|
|
13
|
+
"hint_level": "مستوى التلميح",
|
|
14
|
+
"user_tag": "إجابتك",
|
|
15
|
+
"wrapup_title": "🏆 الحل النهائي وأفضل الممارسات البرمجية",
|
|
16
|
+
},
|
|
17
|
+
"english": {
|
|
18
|
+
"app_title": "🧠 Socratic Debug — Autonomous Mentor",
|
|
19
|
+
"header_title": "🧠 SOCRATIC DEBUG MENTOR",
|
|
20
|
+
"status_active": "● ACTIVE SESSION",
|
|
21
|
+
"status_solved": "✔ SOLVED & CONCLUDED",
|
|
22
|
+
"crash_location": "Location",
|
|
23
|
+
"crashing_line": "Crashing Line",
|
|
24
|
+
"placeholder": "Type your answer here, or type 'exit' / 'q' to quit...",
|
|
25
|
+
"send_btn": "Send",
|
|
26
|
+
"mentor_tag": "Socratic Mentor",
|
|
27
|
+
"hint_level": "Hint Level",
|
|
28
|
+
"user_tag": "Your Response",
|
|
29
|
+
"wrapup_title": "🏆 Socratic Wrap-Up & Defensive Best Practice",
|
|
30
|
+
}
|
|
31
|
+
}
|
ui/views.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from ui.app import bridge, wait_for_gui_user_input
|
|
3
|
+
from ui.console import console
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
from rich.markdown import Markdown
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SocraticUI:
|
|
9
|
+
"""Unified UI layer: seamlessly routes to PyQt6 Desktop Terminal or CLI Fallback."""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def is_gui_active() -> bool:
|
|
13
|
+
return os.environ.get("SOCRATIC_GUI_ACTIVE") == "1"
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def display_crash_banner(error_type: str, error_message: str, file_path: str, line_number: int, line_content: str):
|
|
17
|
+
# In CLI fallback mode, print Rich banner
|
|
18
|
+
if not SocraticUI.is_gui_active():
|
|
19
|
+
console.print(f"\n[bold red]💥 {error_type}: {error_message}[/bold red]")
|
|
20
|
+
console.print(f"[dim]📍 {file_path}:{line_number}[/dim]\n")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def display_mentor_message(content: str, hint_level: int = 1):
|
|
24
|
+
if SocraticUI.is_gui_active():
|
|
25
|
+
bridge.mentor_message_received.emit(content, hint_level)
|
|
26
|
+
else:
|
|
27
|
+
md = Markdown(content)
|
|
28
|
+
panel = Panel(md, title=f"Socratic Mentor (Level {hint_level}/3)", border_style="cyan")
|
|
29
|
+
console.print(panel)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def prompt_user_input() -> str:
|
|
33
|
+
if SocraticUI.is_gui_active():
|
|
34
|
+
return wait_for_gui_user_input()
|
|
35
|
+
else:
|
|
36
|
+
return console.input("[bold yellow]❯❯ Your Response: [/bold yellow]").strip()
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def display_evaluator_verdict(verdict: str):
|
|
40
|
+
if SocraticUI.is_gui_active():
|
|
41
|
+
bridge.evaluator_verdict_received.emit(verdict)
|
|
42
|
+
else:
|
|
43
|
+
console.print(f" [bold yellow]🔍 {verdict}[/bold yellow]\n")
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def display_solution(solution_content: str):
|
|
47
|
+
if SocraticUI.is_gui_active():
|
|
48
|
+
bridge.solution_received.emit(solution_content)
|
|
49
|
+
else:
|
|
50
|
+
md = Markdown(solution_content)
|
|
51
|
+
panel = Panel(md, title="🏆 Socratic Wrap-Up", border_style="green")
|
|
52
|
+
console.print(panel)
|
ui/window.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TerminalWindowLauncher:
|
|
10
|
+
"""
|
|
11
|
+
Cross-platform Terminal Launcher.
|
|
12
|
+
Spawns dedicated, styled GUI terminal popup windows on:
|
|
13
|
+
- Linux (GNOME Terminal, Kitty, Alacritty, Konsole, XFCE, etc.)
|
|
14
|
+
- macOS (Terminal.app, iTerm2 via AppleScript)
|
|
15
|
+
- Windows (Windows Terminal wt.exe, CMD, PowerShell)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
LINUX_TERMINALS = [
|
|
19
|
+
"gnome-terminal",
|
|
20
|
+
"io.elementary.terminal",
|
|
21
|
+
"x-terminal-emulator",
|
|
22
|
+
"xfce4-terminal",
|
|
23
|
+
"mate-terminal",
|
|
24
|
+
"konsole",
|
|
25
|
+
"kitty",
|
|
26
|
+
"alacritty",
|
|
27
|
+
"xterm",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def spawn_session_window(command_args: List[str]) -> bool:
|
|
32
|
+
"""
|
|
33
|
+
Spawns a new dedicated terminal window based on host OS.
|
|
34
|
+
Returns True on success, or False to fallback to in-process execution.
|
|
35
|
+
"""
|
|
36
|
+
# Avoid recursive popup spawning
|
|
37
|
+
if os.environ.get("SOCRATIC_IN_POPUP") == "1":
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
current_os = platform.system().lower()
|
|
41
|
+
env = os.environ.copy()
|
|
42
|
+
env["SOCRATIC_IN_POPUP"] = "1"
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
if "windows" in current_os:
|
|
46
|
+
return TerminalWindowLauncher._spawn_windows(command_args, env)
|
|
47
|
+
elif "darwin" in current_os:
|
|
48
|
+
return TerminalWindowLauncher._spawn_macos(command_args, env)
|
|
49
|
+
elif "linux" in current_os:
|
|
50
|
+
return TerminalWindowLauncher._spawn_linux(command_args, env)
|
|
51
|
+
except Exception:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _spawn_windows(command_args: List[str], env: dict) -> bool:
|
|
58
|
+
cmd_str = " ".join(f'"{arg}"' if " " in arg else arg for arg in command_args)
|
|
59
|
+
|
|
60
|
+
# 1. Try modern Windows Terminal (wt.exe)
|
|
61
|
+
if shutil.which("wt.exe") or shutil.which("wt"):
|
|
62
|
+
subprocess.Popen(
|
|
63
|
+
["wt.exe", "--title", "🧠 Socratic Debug Session", "cmd", "/c", cmd_str],
|
|
64
|
+
env=env,
|
|
65
|
+
creationflags=subprocess.CREATE_NEW_CONSOLE,
|
|
66
|
+
)
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
# 2. Fallback to standard Windows cmd.exe
|
|
70
|
+
subprocess.Popen(
|
|
71
|
+
f'start "🧠 Socratic Debug Session" cmd /c "{cmd_str}"',
|
|
72
|
+
shell=True,
|
|
73
|
+
env=env,
|
|
74
|
+
)
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _spawn_macos(command_args: List[str], env: dict) -> bool:
|
|
79
|
+
cmd_str = " ".join(f'"{arg}"' if " " in arg else arg for arg in command_args)
|
|
80
|
+
escaped_cmd = cmd_str.replace('\\', '\\\\').replace('"', '\\"')
|
|
81
|
+
|
|
82
|
+
# Launch dedicated macOS Terminal.app via osascript
|
|
83
|
+
apple_script = f'''
|
|
84
|
+
tell application "Terminal"
|
|
85
|
+
do script "{escaped_cmd}"
|
|
86
|
+
activate
|
|
87
|
+
end tell
|
|
88
|
+
'''
|
|
89
|
+
subprocess.Popen(["osascript", "-e", apple_script], env=env)
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _spawn_linux(command_args: List[str], env: dict) -> bool:
|
|
94
|
+
terminal = None
|
|
95
|
+
for term in TerminalWindowLauncher.LINUX_TERMINALS:
|
|
96
|
+
if shutil.which(term):
|
|
97
|
+
terminal = term
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
if not terminal:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
if terminal in ("gnome-terminal", "io.elementary.terminal", "x-terminal-emulator", "mate-terminal", "xfce4-terminal"):
|
|
104
|
+
subprocess.Popen(
|
|
105
|
+
[
|
|
106
|
+
terminal,
|
|
107
|
+
"--title=🧠 Socratic Debug Session",
|
|
108
|
+
"--geometry=84x24",
|
|
109
|
+
"--",
|
|
110
|
+
*command_args,
|
|
111
|
+
],
|
|
112
|
+
env=env,
|
|
113
|
+
)
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
elif terminal == "konsole":
|
|
117
|
+
subprocess.Popen(
|
|
118
|
+
[
|
|
119
|
+
terminal,
|
|
120
|
+
"-p", "tabtitle=🧠 Socratic Debug Session",
|
|
121
|
+
"-e",
|
|
122
|
+
*command_args,
|
|
123
|
+
],
|
|
124
|
+
env=env,
|
|
125
|
+
)
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
elif terminal in ("alacritty", "kitty", "xterm"):
|
|
129
|
+
subprocess.Popen(
|
|
130
|
+
[
|
|
131
|
+
terminal,
|
|
132
|
+
"-T", "🧠 Socratic Debug Session",
|
|
133
|
+
"-e",
|
|
134
|
+
*command_args,
|
|
135
|
+
],
|
|
136
|
+
env=env,
|
|
137
|
+
)
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
return False
|