cortex-llm 1.0.9__py3-none-any.whl → 1.0.11__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.
@@ -0,0 +1,61 @@
1
+ """Slash command parsing and dispatch for the CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Callable
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class CommandHandlers:
11
+ show_help: Callable[[], None]
12
+ manage_models: Callable[[str], None]
13
+ download_model: Callable[[str], None]
14
+ clear_conversation: Callable[[], None]
15
+ save_conversation: Callable[[], None]
16
+ show_status: Callable[[], None]
17
+ show_gpu_status: Callable[[], None]
18
+ run_benchmark: Callable[[], None]
19
+ manage_template: Callable[[str], None]
20
+ run_finetune: Callable[[], None]
21
+ hf_login: Callable[[], None]
22
+ show_shortcuts: Callable[[], None]
23
+
24
+
25
+ def handle_command(command: str, handlers: CommandHandlers) -> bool:
26
+ """Handle slash commands. Returns False to exit."""
27
+ parts = command.split(maxsplit=1)
28
+ cmd = parts[0].lower()
29
+ args = parts[1] if len(parts) > 1 else ""
30
+
31
+ if cmd == "/help":
32
+ handlers.show_help()
33
+ elif cmd == "/model":
34
+ handlers.manage_models(args)
35
+ elif cmd == "/download":
36
+ handlers.download_model(args)
37
+ elif cmd == "/clear":
38
+ handlers.clear_conversation()
39
+ elif cmd == "/save":
40
+ handlers.save_conversation()
41
+ elif cmd == "/status":
42
+ handlers.show_status()
43
+ elif cmd == "/gpu":
44
+ handlers.show_gpu_status()
45
+ elif cmd == "/benchmark":
46
+ handlers.run_benchmark()
47
+ elif cmd == "/template":
48
+ handlers.manage_template(args)
49
+ elif cmd == "/finetune":
50
+ handlers.run_finetune()
51
+ elif cmd == "/login":
52
+ handlers.hf_login()
53
+ elif cmd in ["/quit", "/exit"]:
54
+ return False
55
+ elif cmd == "?":
56
+ handlers.show_shortcuts()
57
+ else:
58
+ print(f"\033[31mUnknown command: {cmd}\033[0m")
59
+ print("\033[2mType /help for available commands\033[0m")
60
+
61
+ return True
@@ -0,0 +1,96 @@
1
+ """Prompt formatting helpers for chat templates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from cortex.conversation_manager import MessageRole
6
+
7
+
8
+ def format_prompt_with_chat_template(
9
+ *,
10
+ conversation_manager,
11
+ model_manager,
12
+ template_registry,
13
+ user_input: str,
14
+ include_user: bool = True,
15
+ logger=None,
16
+ ) -> str:
17
+ """Format the prompt with the appropriate chat template for the model."""
18
+ # Get current conversation context
19
+ conversation = conversation_manager.get_current_conversation()
20
+
21
+ # Get the tokenizer for the current model
22
+ model_name = model_manager.current_model
23
+ tokenizer = model_manager.tokenizers.get(model_name)
24
+
25
+ # Build messages list from conversation history
26
+ messages = []
27
+ if conversation and conversation.messages:
28
+ context_messages = conversation.messages[-10:]
29
+ for msg in context_messages:
30
+ messages.append({
31
+ "role": msg.role.value,
32
+ "content": msg.content
33
+ })
34
+
35
+ # Add current user message
36
+ if include_user:
37
+ messages.append({
38
+ "role": "user",
39
+ "content": user_input
40
+ })
41
+
42
+ # Use template registry to format messages
43
+ try:
44
+ profile = template_registry.setup_model(
45
+ model_name,
46
+ tokenizer=tokenizer,
47
+ interactive=False
48
+ )
49
+ formatted = profile.format_messages(messages, add_generation_prompt=True)
50
+ return formatted
51
+
52
+ except (AttributeError, TypeError, ValueError) as e:
53
+ if logger is not None:
54
+ logger.debug(f"Template registry failed: {e}, using fallback")
55
+
56
+ if tokenizer and hasattr(tokenizer, 'apply_chat_template'):
57
+ try:
58
+ formatted = tokenizer.apply_chat_template(
59
+ messages,
60
+ tokenize=False,
61
+ add_generation_prompt=True
62
+ )
63
+ return formatted
64
+ except (AttributeError, TypeError, ValueError) as e:
65
+ if logger is not None:
66
+ logger.debug(f"Tokenizer apply_chat_template failed: {e}")
67
+
68
+ # Fallback: For TinyLlama and other chat models, use the proper format
69
+ if model_name and "chat" in model_name.lower():
70
+ history = ""
71
+ if conversation and conversation.messages:
72
+ recent_messages = conversation.messages[-6:]
73
+ for msg in recent_messages:
74
+ if msg.role == MessageRole.USER:
75
+ history += f"<|user|>\n{msg.content}</s>\n"
76
+ elif msg.role == MessageRole.ASSISTANT:
77
+ history += f"<|assistant|>\n{msg.content}</s>\n"
78
+
79
+ prompt = f"{history}<|user|>\n{user_input}</s>\n<|assistant|>\n"
80
+ return prompt
81
+
82
+ # Generic fallback for non-chat models
83
+ if conversation and len(conversation.messages) > 0:
84
+ context = ""
85
+ recent_messages = conversation.messages[-6:]
86
+ for msg in recent_messages:
87
+ if msg.role == MessageRole.USER:
88
+ context += f"User: {msg.content}\n"
89
+ elif msg.role == MessageRole.ASSISTANT:
90
+ context += f"Assistant: {msg.content}\n"
91
+
92
+ prompt = f"{context}User: {user_input}\nAssistant:"
93
+ else:
94
+ prompt = f"User: {user_input}\nAssistant:"
95
+
96
+ return prompt
cortex/ui/help_ui.py ADDED
@@ -0,0 +1,66 @@
1
+ """Help and shortcuts rendering for CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def show_shortcuts(*, terminal_width: int, box: Any) -> None:
9
+ """Show keyboard shortcuts."""
10
+ width = min(terminal_width - 2, 70)
11
+
12
+ print()
13
+ box.print_box_header("Keyboard Shortcuts", width)
14
+ box.print_empty_line(width)
15
+
16
+ shortcuts = [
17
+ ("Ctrl+C", "Cancel current generation"),
18
+ ("Ctrl+D", "Exit Cortex"),
19
+ ("Tab", "Auto-complete commands"),
20
+ ("/help", "Show all commands"),
21
+ ("?", "Show this help"),
22
+ ]
23
+
24
+ for key, desc in shortcuts:
25
+ colored_key = f"\033[93m{key}\033[0m"
26
+ key_width = len(key)
27
+ padding = " " * (12 - key_width)
28
+ line = f" {colored_key}{padding}{desc}"
29
+ box.print_box_line(line, width)
30
+
31
+ box.print_empty_line(width)
32
+ box.print_box_footer(width)
33
+
34
+
35
+ def show_help(*, terminal_width: int, box: Any) -> None:
36
+ """Show available commands."""
37
+ width = min(terminal_width - 2, 70)
38
+
39
+ print()
40
+ box.print_box_header("Available Commands", width)
41
+ box.print_empty_line(width)
42
+
43
+ commands = [
44
+ ("/help", "Show this help message"),
45
+ ("/status", "Show current setup and GPU info"),
46
+ ("/download", "Download a model from HuggingFace"),
47
+ ("/model", "Manage models (load/delete/info)"),
48
+ ("/finetune", "Fine-tune a model interactively"),
49
+ ("/clear", "Clear conversation history"),
50
+ ("/save", "Save current conversation"),
51
+ ("/template", "Manage chat templates"),
52
+ ("/gpu", "Show GPU status"),
53
+ ("/benchmark", "Run performance benchmark"),
54
+ ("/login", "Login to HuggingFace for gated models"),
55
+ ("/quit", "Exit Cortex"),
56
+ ]
57
+
58
+ for cmd, desc in commands:
59
+ colored_cmd = f"\033[93m{cmd}\033[0m"
60
+ cmd_width = len(cmd)
61
+ padding = " " * (12 - cmd_width)
62
+ line = f" {colored_cmd}{padding}{desc}"
63
+ box.print_box_line(line, width)
64
+
65
+ box.print_empty_line(width)
66
+ box.print_box_footer(width)
cortex/ui/input_box.py ADDED
@@ -0,0 +1,205 @@
1
+ """Input box rendering and protected input handling for CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ import termios
8
+ from typing import Optional
9
+
10
+ from rich.console import Console
11
+
12
+ INPUT_BG = "\033[48;5;236m" # Dark gray background (256-color)
13
+ INPUT_FG = "\033[30m" # Black text
14
+ RESET = "\033[0m"
15
+ PROMPT_PREFIX = " > "
16
+ BOX_HEIGHT = 3
17
+ INPUT_LINE_OFFSET = BOX_HEIGHT // 2
18
+
19
+
20
+ def prompt_input_box(
21
+ *,
22
+ console: Console,
23
+ terminal_width: int,
24
+ current_model_path: Optional[str],
25
+ ) -> str:
26
+ """Render the solid input box, read user input, and clean up the UI."""
27
+ width = terminal_width
28
+
29
+ # ANSI codes
30
+ yellow = "\033[93m"
31
+ dim = "\033[2m"
32
+ clear_line = "\033[2K"
33
+ cursor_up = "\033[A"
34
+ cursor_down = "\033[B"
35
+
36
+ # Model name line
37
+ current_model = ""
38
+ if current_model_path:
39
+ model_name = os.path.basename(current_model_path)
40
+ current_model = f"{dim}Model:{RESET} {yellow}{model_name}{RESET}"
41
+
42
+ # Draw the input box with a solid background (no borders)
43
+ print()
44
+ fill_line = f"{INPUT_BG}{' ' * width}{RESET}"
45
+ for _ in range(BOX_HEIGHT):
46
+ print(fill_line)
47
+
48
+ # Bottom hint: show current model aligned with box
49
+ if current_model:
50
+ print(f"{current_model}")
51
+ else:
52
+ print()
53
+
54
+ # Move cursor to input position inside the box (center line)
55
+ move_up = BOX_HEIGHT - INPUT_LINE_OFFSET + 1
56
+ sys.stdout.write(f"\033[{move_up}A")
57
+ sys.stdout.write(f"\r{INPUT_BG}{INPUT_FG}{PROMPT_PREFIX}")
58
+ sys.stdout.flush()
59
+
60
+ try:
61
+ user_input = _get_protected_input(width)
62
+
63
+ # Clear the input box region using relative moves.
64
+ sys.stdout.write(f"{cursor_down}\r{clear_line}")
65
+ for _ in range(INPUT_LINE_OFFSET + 3):
66
+ sys.stdout.write(f"{cursor_up}\r{clear_line}")
67
+
68
+ # Print the clean prompt that represents the submitted user message.
69
+ sys.stdout.write("\r> " + user_input.strip() + "\n")
70
+ sys.stdout.flush()
71
+
72
+ return user_input.strip()
73
+
74
+ except KeyboardInterrupt:
75
+ raise
76
+ except EOFError:
77
+ try:
78
+ sys.stdout.write(f"\r{clear_line}")
79
+ for _ in range(BOX_HEIGHT - INPUT_LINE_OFFSET):
80
+ sys.stdout.write(f"{cursor_down}\r{clear_line}")
81
+ for _ in range(BOX_HEIGHT):
82
+ sys.stdout.write(f"{cursor_up}\r{clear_line}")
83
+ sys.stdout.flush()
84
+ finally:
85
+ pass
86
+ raise
87
+
88
+
89
+ def _get_protected_input(box_width: int) -> str:
90
+ """Read input in raw mode and prevent deleting the prompt."""
91
+ # Calculate usable width for text (leave one trailing space to avoid wrap)
92
+ max_display_width = box_width - len(PROMPT_PREFIX) - 1
93
+ clear_line = "\033[2K"
94
+ cursor_down = "\033[1B"
95
+ cursor_up = "\033[1A"
96
+
97
+ # Store terminal settings
98
+ old_settings = termios.tcgetattr(sys.stdin)
99
+
100
+ try:
101
+ # Set terminal to raw mode for character-by-character input
102
+ # Disable ISIG so we can handle Ctrl+C manually for clean exit
103
+ new_settings = termios.tcgetattr(sys.stdin)
104
+ new_settings[3] = new_settings[3] & ~termios.ICANON
105
+ new_settings[3] = new_settings[3] & ~termios.ECHO
106
+ new_settings[3] = new_settings[3] & ~termios.ISIG
107
+ new_settings[6][termios.VMIN] = 1
108
+ new_settings[6][termios.VTIME] = 0
109
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
110
+
111
+ input_buffer = []
112
+ cursor_pos = 0
113
+ view_offset = 0
114
+
115
+ def redraw_line():
116
+ nonlocal view_offset
117
+
118
+ if len(input_buffer) <= max_display_width:
119
+ display_text = "".join(input_buffer)
120
+ display_cursor_pos = cursor_pos
121
+ else:
122
+ if cursor_pos < view_offset:
123
+ view_offset = cursor_pos
124
+ elif cursor_pos >= view_offset + max_display_width:
125
+ view_offset = cursor_pos - max_display_width + 1
126
+
127
+ display_text = "".join(input_buffer[view_offset:view_offset + max_display_width])
128
+ display_cursor_pos = cursor_pos - view_offset
129
+
130
+ pad_len = box_width - len(PROMPT_PREFIX) - len(display_text)
131
+ if pad_len < 0:
132
+ pad_len = 0
133
+
134
+ sys.stdout.write(
135
+ f"\r{INPUT_BG}{INPUT_FG}{PROMPT_PREFIX}{display_text}"
136
+ f"{' ' * pad_len}{RESET}"
137
+ )
138
+
139
+ cursor_column = len(PROMPT_PREFIX) + 1 + display_cursor_pos
140
+ sys.stdout.write(f"\033[{cursor_column}G")
141
+ sys.stdout.flush()
142
+
143
+ redraw_line()
144
+
145
+ def clear_box_from_input():
146
+ sys.stdout.write(f"\r{clear_line}")
147
+ for _ in range(BOX_HEIGHT - INPUT_LINE_OFFSET):
148
+ sys.stdout.write(f"{cursor_down}\r{clear_line}")
149
+ for _ in range(BOX_HEIGHT):
150
+ sys.stdout.write(f"{cursor_up}\r{clear_line}")
151
+ sys.stdout.write("\r")
152
+ sys.stdout.flush()
153
+
154
+ while True:
155
+ char = sys.stdin.read(1)
156
+
157
+ if char == "\r" or char == "\n":
158
+ sys.stdout.write("\r\n")
159
+ sys.stdout.write("\r\n")
160
+ sys.stdout.flush()
161
+ break
162
+
163
+ if char == "\x7f" or char == "\x08":
164
+ if cursor_pos > 0:
165
+ cursor_pos -= 1
166
+ input_buffer.pop(cursor_pos)
167
+ redraw_line()
168
+ continue
169
+
170
+ if char == "\x03":
171
+ clear_box_from_input()
172
+ raise KeyboardInterrupt
173
+
174
+ if char == "\x04":
175
+ raise EOFError
176
+
177
+ if char == "\x1b":
178
+ next1 = sys.stdin.read(1)
179
+ if next1 == "[":
180
+ next2 = sys.stdin.read(1)
181
+ if next2 == "D":
182
+ if cursor_pos > 0:
183
+ cursor_pos -= 1
184
+ redraw_line()
185
+ elif next2 == "C":
186
+ if cursor_pos < len(input_buffer):
187
+ cursor_pos += 1
188
+ redraw_line()
189
+ elif next2 == "H":
190
+ cursor_pos = 0
191
+ view_offset = 0
192
+ redraw_line()
193
+ elif next2 == "F":
194
+ cursor_pos = len(input_buffer)
195
+ redraw_line()
196
+ continue
197
+
198
+ if ord(char) >= 32:
199
+ input_buffer.insert(cursor_pos, char)
200
+ cursor_pos += 1
201
+ redraw_line()
202
+
203
+ return "".join(input_buffer)
204
+ finally:
205
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)