829 lines
28 KiB
Python
829 lines
28 KiB
Python
"""Dialog-based UI for managing SmartTools."""
|
|
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from typing import Optional, Tuple, List
|
|
|
|
from .tool import (
|
|
Tool, ToolArgument, PromptStep, CodeStep, Step,
|
|
list_tools, load_tool, save_tool, delete_tool, tool_exists
|
|
)
|
|
from .providers import Provider, load_providers, add_provider, delete_provider, get_provider
|
|
|
|
|
|
def _check_urwid() -> bool:
|
|
"""Check if urwid is available (preferred - has mouse support)."""
|
|
try:
|
|
import urwid
|
|
return True
|
|
except ImportError:
|
|
return False
|
|
|
|
|
|
def _check_snack() -> bool:
|
|
"""Check if snack (python3-newt) is available."""
|
|
try:
|
|
if '/usr/lib/python3/dist-packages' not in sys.path:
|
|
sys.path.insert(0, '/usr/lib/python3/dist-packages')
|
|
import snack
|
|
return True
|
|
except ImportError:
|
|
return False
|
|
|
|
|
|
def check_dialog() -> str:
|
|
"""Check for available dialog program. Returns 'dialog', 'whiptail', or None."""
|
|
for prog in ["dialog", "whiptail"]:
|
|
try:
|
|
subprocess.run([prog, "--version"], capture_output=True, check=False)
|
|
return prog
|
|
except FileNotFoundError:
|
|
continue
|
|
return None
|
|
|
|
|
|
def run_dialog(args: list[str], dialog_prog: str = "dialog") -> Tuple[int, str]:
|
|
"""Run a dialog command and return (exit_code, output)."""
|
|
try:
|
|
if dialog_prog == "whiptail":
|
|
result = subprocess.run(
|
|
[dialog_prog] + args,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
return result.returncode, result.stderr.strip()
|
|
else:
|
|
cmd_with_stdout = [dialog_prog, "--stdout"] + args
|
|
result = subprocess.run(
|
|
cmd_with_stdout,
|
|
stdout=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
return result.returncode, result.stdout.strip()
|
|
except Exception as e:
|
|
return 1, ""
|
|
|
|
|
|
def show_menu(title: str, choices: list[tuple[str, str]], dialog_prog: str) -> Optional[str]:
|
|
"""Show a menu and return the selected item."""
|
|
args = ["--title", title, "--menu", "Choose an option:", "20", "75", str(len(choices))]
|
|
for tag, desc in choices:
|
|
args.extend([tag, desc])
|
|
|
|
code, output = run_dialog(args, dialog_prog)
|
|
return output if code == 0 else None
|
|
|
|
|
|
def show_input(title: str, prompt: str, initial: str = "", dialog_prog: str = "dialog") -> Optional[str]:
|
|
"""Show an input box and return the entered text."""
|
|
args = ["--title", title, "--inputbox", prompt, "10", "60", initial]
|
|
code, output = run_dialog(args, dialog_prog)
|
|
return output if code == 0 else None
|
|
|
|
|
|
def show_textbox(title: str, text: str, dialog_prog: str = "dialog") -> Optional[str]:
|
|
"""Show a text editor for multi-line input."""
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
f.write(text)
|
|
temp_path = f.name
|
|
|
|
try:
|
|
args = ["--title", title, "--editbox", temp_path, "20", "75"]
|
|
code, output = run_dialog(args, dialog_prog)
|
|
return output if code == 0 else None
|
|
finally:
|
|
import os
|
|
os.unlink(temp_path)
|
|
|
|
|
|
def show_yesno(title: str, text: str, dialog_prog: str = "dialog") -> bool:
|
|
"""Show a yes/no dialog."""
|
|
args = ["--title", title, "--yesno", text, "10", "60"]
|
|
code, _ = run_dialog(args, dialog_prog)
|
|
return code == 0
|
|
|
|
|
|
def show_message(title: str, text: str, dialog_prog: str = "dialog"):
|
|
"""Show a message box."""
|
|
args = ["--title", title, "--msgbox", text, "15", "70"]
|
|
run_dialog(args, dialog_prog)
|
|
|
|
|
|
def show_mixed_form(title: str, fields: dict, dialog_prog: str, height: int = 20) -> Optional[dict]:
|
|
"""Show a form with multiple fields using --mixedform."""
|
|
args = ["--title", title, "--mixedform",
|
|
"Tab: next field | Enter: submit | Esc: cancel",
|
|
str(height), "75", "0"]
|
|
|
|
field_names = list(fields.keys())
|
|
y = 1
|
|
for name in field_names:
|
|
label, initial, field_type = fields[name]
|
|
args.extend([
|
|
label, str(y), "1",
|
|
initial, str(y), "18",
|
|
"52", "256", str(field_type)
|
|
])
|
|
y += 1
|
|
|
|
code, output = run_dialog(args, dialog_prog)
|
|
|
|
if code != 0:
|
|
return None
|
|
|
|
values = output.split('\n')
|
|
result = {}
|
|
for i, name in enumerate(field_names):
|
|
result[name] = values[i] if i < len(values) else ""
|
|
|
|
return result
|
|
|
|
|
|
# ============ Provider Management ============
|
|
|
|
def select_provider(dialog_prog: str) -> Optional[str]:
|
|
"""Show provider selection menu with option to create new."""
|
|
providers = load_providers()
|
|
|
|
choices = [(p.name, f"{p.description} ({p.command})") for p in providers]
|
|
choices.append(("__new__", "[ + Add New Provider ]"))
|
|
|
|
selected = show_menu("Select Provider", choices, dialog_prog)
|
|
|
|
if selected == "__new__":
|
|
provider = create_provider_form(dialog_prog)
|
|
if provider:
|
|
add_provider(provider)
|
|
return provider.name
|
|
return None
|
|
|
|
return selected
|
|
|
|
|
|
def create_provider_form(dialog_prog: str, existing: Optional[Provider] = None) -> Optional[Provider]:
|
|
"""Show form for creating/editing a provider."""
|
|
title = f"Edit Provider: {existing.name}" if existing else "Add New Provider"
|
|
|
|
fields = {
|
|
"name": (
|
|
"Name:",
|
|
existing.name if existing else "",
|
|
2 if existing else 0 # readonly if editing
|
|
),
|
|
"command": (
|
|
"Command:",
|
|
existing.command if existing else "",
|
|
0
|
|
),
|
|
"description": (
|
|
"Description:",
|
|
existing.description if existing else "",
|
|
0
|
|
),
|
|
}
|
|
|
|
result = show_mixed_form(title, fields, dialog_prog, height=12)
|
|
|
|
if not result:
|
|
return None
|
|
|
|
name = result["name"].strip()
|
|
command = result["command"].strip()
|
|
|
|
if not name:
|
|
show_message("Error", "Provider name is required.", dialog_prog)
|
|
return None
|
|
|
|
if not command:
|
|
show_message("Error", "Command is required.", dialog_prog)
|
|
return None
|
|
|
|
return Provider(
|
|
name=name,
|
|
command=command,
|
|
description=result["description"].strip()
|
|
)
|
|
|
|
|
|
def ui_manage_providers(dialog_prog: str):
|
|
"""Manage providers menu."""
|
|
while True:
|
|
providers = load_providers()
|
|
choices = [(p.name, f"{p.command}") for p in providers]
|
|
choices.append(("__add__", "[ + Add New Provider ]"))
|
|
choices.append(("__back__", "[ <- Back to Main Menu ]"))
|
|
|
|
selected = show_menu("Manage Providers", choices, dialog_prog)
|
|
|
|
if selected is None or selected == "__back__":
|
|
break
|
|
elif selected == "__add__":
|
|
provider = create_provider_form(dialog_prog)
|
|
if provider:
|
|
add_provider(provider)
|
|
show_message("Success", f"Provider '{provider.name}' added.", dialog_prog)
|
|
else:
|
|
# Edit or delete existing provider
|
|
provider = get_provider(selected)
|
|
if provider:
|
|
action = show_menu(
|
|
f"Provider: {selected}",
|
|
[
|
|
("edit", "Edit provider"),
|
|
("delete", "Delete provider"),
|
|
("back", "Back"),
|
|
],
|
|
dialog_prog
|
|
)
|
|
|
|
if action == "edit":
|
|
updated = create_provider_form(dialog_prog, provider)
|
|
if updated:
|
|
add_provider(updated)
|
|
show_message("Success", f"Provider '{updated.name}' updated.", dialog_prog)
|
|
elif action == "delete":
|
|
if show_yesno("Confirm", f"Delete provider '{selected}'?", dialog_prog):
|
|
delete_provider(selected)
|
|
show_message("Deleted", f"Provider '{selected}' deleted.", dialog_prog)
|
|
|
|
|
|
# ============ Tool Builder UI ============
|
|
|
|
def format_tool_summary(tool: Tool) -> str:
|
|
"""Format a summary of the tool's components."""
|
|
lines = []
|
|
lines.append(f"Name: {tool.name}")
|
|
lines.append(f"Description: {tool.description or '(none)'}")
|
|
lines.append("")
|
|
|
|
if tool.arguments:
|
|
lines.append("Arguments:")
|
|
for arg in tool.arguments:
|
|
default = f" = {arg.default}" if arg.default else ""
|
|
lines.append(f" {arg.flag} -> {{{arg.variable}}}{default}")
|
|
lines.append("")
|
|
|
|
if tool.steps:
|
|
lines.append("Steps:")
|
|
for i, step in enumerate(tool.steps):
|
|
if isinstance(step, PromptStep):
|
|
preview = step.prompt[:40].replace('\n', ' ') + "..."
|
|
lines.append(f" {i+1}. PROMPT [{step.provider}] -> {{{step.output_var}}}")
|
|
lines.append(f" {preview}")
|
|
elif isinstance(step, CodeStep):
|
|
preview = step.code[:40].replace('\n', ' ') + "..."
|
|
lines.append(f" {i+1}. CODE -> {{{step.output_var}}}")
|
|
lines.append(f" {preview}")
|
|
lines.append("")
|
|
|
|
lines.append(f"Output: {tool.output}")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def get_available_variables(tool: Tool, up_to_step: int = -1) -> List[str]:
|
|
"""Get list of available variables at a given point in the tool."""
|
|
variables = ["input"]
|
|
|
|
for arg in tool.arguments:
|
|
variables.append(arg.variable)
|
|
|
|
if up_to_step == -1:
|
|
up_to_step = len(tool.steps)
|
|
|
|
for i, step in enumerate(tool.steps):
|
|
if i >= up_to_step:
|
|
break
|
|
variables.append(step.output_var)
|
|
|
|
return variables
|
|
|
|
|
|
def edit_argument(dialog_prog: str, existing: Optional[ToolArgument] = None) -> Optional[ToolArgument]:
|
|
"""Edit or create an argument."""
|
|
title = f"Edit Argument: {existing.flag}" if existing else "Add Argument"
|
|
|
|
fields = {
|
|
"flag": ("Flag:", existing.flag if existing else "--", 0),
|
|
"variable": ("Variable:", existing.variable if existing else "", 0),
|
|
"default": ("Default:", existing.default or "" if existing else "", 0),
|
|
"description": ("Description:", existing.description if existing else "", 0),
|
|
}
|
|
|
|
result = show_mixed_form(title, fields, dialog_prog, height=14)
|
|
if not result:
|
|
return None
|
|
|
|
flag = result["flag"].strip()
|
|
variable = result["variable"].strip()
|
|
|
|
if not flag:
|
|
show_message("Error", "Flag is required (e.g., --max-size).", dialog_prog)
|
|
return None
|
|
|
|
if not variable:
|
|
# Auto-generate variable name from flag
|
|
variable = flag.lstrip("-").replace("-", "_")
|
|
|
|
return ToolArgument(
|
|
flag=flag,
|
|
variable=variable,
|
|
default=result["default"].strip() or None,
|
|
description=result["description"].strip()
|
|
)
|
|
|
|
|
|
def edit_prompt_step(dialog_prog: str, existing: Optional[PromptStep] = None,
|
|
available_vars: List[str] = None) -> Optional[PromptStep]:
|
|
"""Edit or create a prompt step."""
|
|
title = "Edit Prompt Step" if existing else "Add Prompt Step"
|
|
|
|
# First, select provider
|
|
provider = select_provider(dialog_prog)
|
|
if not provider:
|
|
provider = existing.provider if existing else "mock"
|
|
|
|
# Show variable help
|
|
var_help = "Available: " + ", ".join(f"{{{v}}}" for v in (available_vars or ["input"]))
|
|
|
|
# Edit prompt text
|
|
default_prompt = existing.prompt if existing else f"Process this input:\n\n{{input}}"
|
|
prompt = show_textbox(f"Prompt Template\n{var_help}", default_prompt, dialog_prog)
|
|
if prompt is None:
|
|
return None
|
|
|
|
# Get output variable
|
|
output_var = show_input(
|
|
"Output Variable",
|
|
"Variable name to store the result:",
|
|
existing.output_var if existing else "result",
|
|
dialog_prog
|
|
)
|
|
if not output_var:
|
|
return None
|
|
|
|
return PromptStep(
|
|
prompt=prompt,
|
|
provider=provider,
|
|
output_var=output_var.strip()
|
|
)
|
|
|
|
|
|
def edit_code_step(dialog_prog: str, existing: Optional[CodeStep] = None,
|
|
available_vars: List[str] = None) -> Optional[CodeStep]:
|
|
"""Edit or create a code step."""
|
|
title = "Edit Code Step" if existing else "Add Code Step"
|
|
|
|
# Show variable help
|
|
var_help = "Variables: " + ", ".join(available_vars or ["input"])
|
|
var_help += "\nSet 'result' variable for output"
|
|
|
|
# Edit code
|
|
default_code = existing.code if existing else "# Available variables: " + ", ".join(available_vars or ["input"]) + "\n# Set 'result' for output\nresult = input.upper()"
|
|
code = show_textbox(f"Python Code\n{var_help}", default_code, dialog_prog)
|
|
if code is None:
|
|
return None
|
|
|
|
# Get output variable
|
|
output_var = show_input(
|
|
"Output Variable",
|
|
"Variable name to store the result:",
|
|
existing.output_var if existing else "processed",
|
|
dialog_prog
|
|
)
|
|
if not output_var:
|
|
return None
|
|
|
|
return CodeStep(
|
|
code=code,
|
|
output_var=output_var.strip()
|
|
)
|
|
|
|
|
|
def edit_tool_info(tool: Tool, is_edit: bool, dialog_prog: str) -> None:
|
|
"""Edit basic tool info (name, description, output)."""
|
|
while True:
|
|
# Build info section menu
|
|
output_preview = tool.output[:35] + "..." if len(tool.output) > 35 else tool.output
|
|
args_count = len(tool.arguments)
|
|
args_summary = f"({args_count} defined)" if args_count else "(none)"
|
|
|
|
choices = [
|
|
("name", f"Name: {tool.name or '(not set)'}"),
|
|
("desc", f"Description: {tool.description[:35] + '...' if len(tool.description) > 35 else tool.description or '(none)'}"),
|
|
("---1", "─" * 40),
|
|
]
|
|
|
|
# Show arguments
|
|
if tool.arguments:
|
|
for i, arg in enumerate(tool.arguments):
|
|
default = f" = {arg.default}" if arg.default else ""
|
|
choices.append((f"arg_{i}", f" {arg.flag} -> {{{arg.variable}}}{default}"))
|
|
choices.append(("add_arg", " [ + Add Argument ]"))
|
|
|
|
choices.append(("---2", "─" * 40))
|
|
choices.append(("output", f"Output Template: {output_preview}"))
|
|
choices.append(("---3", "─" * 40))
|
|
choices.append(("back", "<- Back to Tool Builder"))
|
|
|
|
selected = show_menu("Tool Info & Arguments", choices, dialog_prog)
|
|
|
|
if selected is None or selected == "back":
|
|
break
|
|
|
|
elif selected == "name":
|
|
if is_edit:
|
|
show_message("Info", "Cannot change tool name after creation.", dialog_prog)
|
|
else:
|
|
new_name = show_input("Tool Name", "Enter tool name:", tool.name, dialog_prog)
|
|
if new_name:
|
|
tool.name = new_name.strip()
|
|
|
|
elif selected == "desc":
|
|
new_desc = show_input("Description", "Enter tool description:", tool.description, dialog_prog)
|
|
if new_desc is not None:
|
|
tool.description = new_desc.strip()
|
|
|
|
elif selected == "add_arg":
|
|
arg = edit_argument(dialog_prog)
|
|
if arg:
|
|
tool.arguments.append(arg)
|
|
|
|
elif selected.startswith("arg_"):
|
|
idx = int(selected[4:])
|
|
arg = tool.arguments[idx]
|
|
action = show_menu(
|
|
f"Argument: {arg.flag}",
|
|
[("edit", "Edit"), ("delete", "Delete"), ("back", "Back")],
|
|
dialog_prog
|
|
)
|
|
if action == "edit":
|
|
updated = edit_argument(dialog_prog, arg)
|
|
if updated:
|
|
tool.arguments[idx] = updated
|
|
elif action == "delete":
|
|
if show_yesno("Delete", f"Delete argument {arg.flag}?", dialog_prog):
|
|
tool.arguments.pop(idx)
|
|
|
|
elif selected == "output":
|
|
available = get_available_variables(tool)
|
|
var_help = "Variables: " + ", ".join(f"{{{v}}}" for v in available)
|
|
new_output = show_textbox(f"Output Template\n{var_help}", tool.output, dialog_prog)
|
|
if new_output is not None:
|
|
tool.output = new_output
|
|
|
|
|
|
def edit_tool_steps(tool: Tool, dialog_prog: str) -> None:
|
|
"""Edit tool processing steps."""
|
|
while True:
|
|
# Build steps section menu
|
|
choices = []
|
|
|
|
if tool.steps:
|
|
for i, step in enumerate(tool.steps):
|
|
if isinstance(step, PromptStep):
|
|
choices.append((f"step_{i}", f"{i+1}. PROMPT [{step.provider}] -> {{{step.output_var}}}"))
|
|
elif isinstance(step, CodeStep):
|
|
choices.append((f"step_{i}", f"{i+1}. CODE -> {{{step.output_var}}}"))
|
|
else:
|
|
choices.append(("none", "(no steps defined)"))
|
|
|
|
choices.append(("---1", "─" * 40))
|
|
choices.append(("add_prompt", "[ + Add Prompt Step ]"))
|
|
choices.append(("add_code", "[ + Add Code Step ]"))
|
|
choices.append(("---2", "─" * 40))
|
|
choices.append(("back", "<- Back to Tool Builder"))
|
|
|
|
selected = show_menu("Processing Steps", choices, dialog_prog)
|
|
|
|
if selected is None or selected == "back" or selected == "none":
|
|
if selected == "none":
|
|
continue
|
|
break
|
|
|
|
elif selected == "add_prompt":
|
|
available = get_available_variables(tool)
|
|
step = edit_prompt_step(dialog_prog, available_vars=available)
|
|
if step:
|
|
tool.steps.append(step)
|
|
|
|
elif selected == "add_code":
|
|
available = get_available_variables(tool)
|
|
step = edit_code_step(dialog_prog, available_vars=available)
|
|
if step:
|
|
tool.steps.append(step)
|
|
|
|
elif selected.startswith("step_"):
|
|
idx = int(selected[5:])
|
|
step = tool.steps[idx]
|
|
step_type = "Prompt" if isinstance(step, PromptStep) else "Code"
|
|
|
|
move_choices = [("edit", "Edit"), ("delete", "Delete")]
|
|
if idx > 0:
|
|
move_choices.insert(1, ("move_up", "Move Up"))
|
|
if idx < len(tool.steps) - 1:
|
|
move_choices.insert(2 if idx > 0 else 1, ("move_down", "Move Down"))
|
|
move_choices.append(("back", "Back"))
|
|
|
|
action = show_menu(f"Step {idx+1}: {step_type}", move_choices, dialog_prog)
|
|
|
|
if action == "edit":
|
|
available = get_available_variables(tool, idx)
|
|
if isinstance(step, PromptStep):
|
|
updated = edit_prompt_step(dialog_prog, step, available)
|
|
else:
|
|
updated = edit_code_step(dialog_prog, step, available)
|
|
if updated:
|
|
tool.steps[idx] = updated
|
|
elif action == "move_up" and idx > 0:
|
|
tool.steps[idx], tool.steps[idx-1] = tool.steps[idx-1], tool.steps[idx]
|
|
elif action == "move_down" and idx < len(tool.steps) - 1:
|
|
tool.steps[idx], tool.steps[idx+1] = tool.steps[idx+1], tool.steps[idx]
|
|
elif action == "delete":
|
|
if show_yesno("Delete", f"Delete step {idx+1}?", dialog_prog):
|
|
tool.steps.pop(idx)
|
|
|
|
|
|
def tool_builder(dialog_prog: str, existing: Optional[Tool] = None) -> Optional[Tool]:
|
|
"""Main tool builder interface with tabbed sections."""
|
|
is_edit = existing is not None
|
|
|
|
# Initialize tool
|
|
if existing:
|
|
tool = Tool(
|
|
name=existing.name,
|
|
description=existing.description,
|
|
arguments=list(existing.arguments),
|
|
steps=list(existing.steps),
|
|
output=existing.output
|
|
)
|
|
else:
|
|
tool = Tool(name="", description="", arguments=[], steps=[], output="{input}")
|
|
|
|
while True:
|
|
# Build main menu with section summaries
|
|
args_count = len(tool.arguments)
|
|
steps_count = len(tool.steps)
|
|
|
|
# Info summary
|
|
name_display = tool.name or "(not set)"
|
|
info_summary = f"{name_display}"
|
|
if tool.arguments:
|
|
info_summary += f" | {args_count} arg{'s' if args_count != 1 else ''}"
|
|
|
|
# Steps summary
|
|
if tool.steps:
|
|
step_types = []
|
|
for s in tool.steps:
|
|
if isinstance(s, PromptStep):
|
|
step_types.append(f"P:{s.provider}")
|
|
else:
|
|
step_types.append("C")
|
|
steps_summary = " -> ".join(step_types)
|
|
else:
|
|
steps_summary = "(none)"
|
|
|
|
choices = [
|
|
("info", f"[1] Info & Args : {info_summary}"),
|
|
("steps", f"[2] Steps : {steps_summary}"),
|
|
("---", "─" * 50),
|
|
("preview", "Preview Full Summary"),
|
|
("save", "Save Tool"),
|
|
("cancel", "Cancel"),
|
|
]
|
|
|
|
title = f"Tool Builder: {tool.name}" if tool.name else "Tool Builder: New Tool"
|
|
selected = show_menu(title, choices, dialog_prog)
|
|
|
|
if selected is None or selected == "cancel":
|
|
if show_yesno("Cancel", "Discard changes?", dialog_prog):
|
|
return None
|
|
continue
|
|
|
|
elif selected == "info":
|
|
edit_tool_info(tool, is_edit, dialog_prog)
|
|
|
|
elif selected == "steps":
|
|
edit_tool_steps(tool, dialog_prog)
|
|
|
|
elif selected == "preview":
|
|
summary = format_tool_summary(tool)
|
|
show_message("Tool Summary", summary, dialog_prog)
|
|
|
|
elif selected == "save":
|
|
if not tool.name:
|
|
show_message("Error", "Tool name is required. Go to Info & Args to set it.", dialog_prog)
|
|
continue
|
|
|
|
if not is_edit and tool_exists(tool.name):
|
|
if not show_yesno("Overwrite?", f"Tool '{tool.name}' exists. Overwrite?", dialog_prog):
|
|
continue
|
|
|
|
return tool
|
|
|
|
|
|
# ============ Main Menu Functions ============
|
|
|
|
def ui_list_tools(dialog_prog: str):
|
|
"""Show list of tools."""
|
|
tools = list_tools()
|
|
if not tools:
|
|
show_message("Tools", "No tools found.\n\nCreate your first tool from the main menu.", dialog_prog)
|
|
return
|
|
|
|
text = "Available tools:\n\n"
|
|
for name in tools:
|
|
tool = load_tool(name)
|
|
if tool:
|
|
text += f" {name}: {tool.description or 'No description'}\n"
|
|
if tool.arguments:
|
|
args = ", ".join(arg.flag for arg in tool.arguments)
|
|
text += f" Arguments: {args}\n"
|
|
if tool.steps:
|
|
step_info = []
|
|
for step in tool.steps:
|
|
if isinstance(step, PromptStep):
|
|
step_info.append(f"PROMPT[{step.provider}]")
|
|
else:
|
|
step_info.append("CODE")
|
|
text += f" Steps: {' -> '.join(step_info)}\n"
|
|
text += "\n"
|
|
|
|
show_message("Tools", text, dialog_prog)
|
|
|
|
|
|
def ui_create_tool(dialog_prog: str):
|
|
"""Create a new tool."""
|
|
tool = tool_builder(dialog_prog)
|
|
if tool:
|
|
path = save_tool(tool)
|
|
|
|
# Build usage example
|
|
usage = f"{tool.name}"
|
|
for arg in tool.arguments:
|
|
if arg.default:
|
|
usage += f" [{arg.flag} <{arg.variable}>]"
|
|
else:
|
|
usage += f" {arg.flag} <{arg.variable}>"
|
|
usage += " < input.txt"
|
|
|
|
show_message("Success",
|
|
f"Tool '{tool.name}' created!\n\n"
|
|
f"Config: {path}\n\n"
|
|
f"Usage: {usage}",
|
|
dialog_prog)
|
|
|
|
|
|
def ui_edit_tool(dialog_prog: str):
|
|
"""Edit an existing tool."""
|
|
tools = list_tools()
|
|
if not tools:
|
|
show_message("Edit Tool", "No tools found.", dialog_prog)
|
|
return
|
|
|
|
choices = []
|
|
for name in tools:
|
|
tool = load_tool(name)
|
|
desc = tool.description if tool else "No description"
|
|
choices.append((name, desc))
|
|
|
|
selected = show_menu("Select Tool to Edit", choices, dialog_prog)
|
|
|
|
if selected:
|
|
existing = load_tool(selected)
|
|
if existing:
|
|
tool = tool_builder(dialog_prog, existing)
|
|
if tool:
|
|
save_tool(tool)
|
|
show_message("Success", f"Tool '{tool.name}' updated!", dialog_prog)
|
|
|
|
|
|
def ui_delete_tool(dialog_prog: str):
|
|
"""Delete a tool."""
|
|
tools = list_tools()
|
|
if not tools:
|
|
show_message("Delete Tool", "No tools found.", dialog_prog)
|
|
return
|
|
|
|
choices = []
|
|
for name in tools:
|
|
tool = load_tool(name)
|
|
desc = tool.description if tool else "No description"
|
|
choices.append((name, desc))
|
|
|
|
selected = show_menu("Select Tool to Delete", choices, dialog_prog)
|
|
|
|
if selected:
|
|
if show_yesno("Confirm Delete", f"Delete tool '{selected}'?\n\nThis cannot be undone.", dialog_prog):
|
|
if delete_tool(selected):
|
|
show_message("Deleted", f"Tool '{selected}' deleted.", dialog_prog)
|
|
else:
|
|
show_message("Error", f"Failed to delete '{selected}'.", dialog_prog)
|
|
|
|
|
|
def ui_test_tool(dialog_prog: str):
|
|
"""Test a tool with mock provider."""
|
|
tools = list_tools()
|
|
if not tools:
|
|
show_message("Test Tool", "No tools found.", dialog_prog)
|
|
return
|
|
|
|
choices = []
|
|
for name in tools:
|
|
tool = load_tool(name)
|
|
desc = tool.description if tool else "No description"
|
|
choices.append((name, desc))
|
|
|
|
selected = show_menu("Select Tool to Test", choices, dialog_prog)
|
|
|
|
if selected:
|
|
tool = load_tool(selected)
|
|
if tool:
|
|
test_input = show_textbox("Test Input", "Enter test input here...", dialog_prog)
|
|
if test_input:
|
|
from .runner import run_tool
|
|
output, code = run_tool(
|
|
tool=tool,
|
|
input_text=test_input,
|
|
custom_args={},
|
|
provider_override="mock",
|
|
dry_run=False,
|
|
show_prompt=False,
|
|
verbose=False
|
|
)
|
|
result_text = f"Exit code: {code}\n\n--- Output ---\n{output[:1000]}"
|
|
if len(output) > 1000:
|
|
result_text += "\n... (truncated)"
|
|
show_message("Test Result", result_text, dialog_prog)
|
|
|
|
|
|
def main_menu(dialog_prog: str):
|
|
"""Show the main menu."""
|
|
while True:
|
|
choice = show_menu(
|
|
"SmartTools Manager",
|
|
[
|
|
("list", "List all tools"),
|
|
("create", "Create new tool"),
|
|
("edit", "Edit existing tool"),
|
|
("delete", "Delete tool"),
|
|
("test", "Test tool (mock provider)"),
|
|
("providers", "Manage providers"),
|
|
("exit", "Exit"),
|
|
],
|
|
dialog_prog
|
|
)
|
|
|
|
if choice is None or choice == "exit":
|
|
break
|
|
elif choice == "list":
|
|
ui_list_tools(dialog_prog)
|
|
elif choice == "create":
|
|
ui_create_tool(dialog_prog)
|
|
elif choice == "edit":
|
|
ui_edit_tool(dialog_prog)
|
|
elif choice == "delete":
|
|
ui_delete_tool(dialog_prog)
|
|
elif choice == "test":
|
|
ui_test_tool(dialog_prog)
|
|
elif choice == "providers":
|
|
ui_manage_providers(dialog_prog)
|
|
|
|
|
|
def run_ui():
|
|
"""Entry point for the UI."""
|
|
# Prefer urwid (has mouse support)
|
|
if _check_urwid():
|
|
from .ui_urwid import run_ui as run_urwid_ui
|
|
run_urwid_ui()
|
|
return
|
|
|
|
# Fallback to snack (BIOS-style)
|
|
if _check_snack():
|
|
from .ui_snack import run_ui as run_snack_ui
|
|
run_snack_ui()
|
|
return
|
|
|
|
# Fallback to dialog/whiptail
|
|
dialog_prog = check_dialog()
|
|
|
|
if not dialog_prog:
|
|
print("Error: No TUI library found.", file=sys.stderr)
|
|
print("Install one of:", file=sys.stderr)
|
|
print(" pip install urwid (recommended - has mouse support)", file=sys.stderr)
|
|
print(" sudo apt install python3-newt", file=sys.stderr)
|
|
print(" sudo apt install dialog", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
main_menu(dialog_prog)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
finally:
|
|
subprocess.run(["clear"], check=False)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_ui()
|