Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies = [
"sentence-transformers>=3.4.1",
"tqdm>=4.67.1",
"trame>=3.9.0",
"trame-code>=1.0.2",
"trame-vtk>=2.8.0",
"trame-vuetify>=3",
"tree_sitter>=0.23",
Expand Down Expand Up @@ -144,6 +145,7 @@ module = [
"vtkmodules.*",
"trame.*",
"trame_vtk.*",
"trame_code.*",
"openai.*",
"click.*",
"chromadb.*",
Expand Down
52 changes: 52 additions & 0 deletions src/vtk_prompt/controllers/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,58 @@ def save_conversation(app: Any) -> str:
return ""


def copy_generated_code(app: Any) -> None:
"""Copy generated code to clipboard via client-side trigger."""
code = app.state.generated_code
if code:
app.server.js_call("trame.utils.vtk_prompt.copy_to_clipboard", code)


def download_generated_code(app: Any) -> str:
"""Download generated code as a .py file with VTK renderer initialization."""
code = app.state.generated_code
if not code:
return ""

# Remove the injected renderer comment if present
code_lines = code.split("\n")
filtered_lines = [
line
for line in code_lines
if not line.strip().startswith("# renderer is a vtkRenderer")
and not line.strip().startswith("# Use your own vtkRenderer")
]
clean_code = "\n".join(filtered_lines)

# Add standalone VTK renderer initialization
standalone_code = """import vtk

# Create renderer, render window, and interactor
renderer = vtk.vtkRenderer()
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window_interactor = vtk.vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)

# Set background color
renderer.SetBackground(0.1, 0.1, 0.1)

"""

# Add the generated code
standalone_code += clean_code

# Add render window display code
standalone_code += """

# Display the render window
render_window.Render()
render_window_interactor.Start()
"""

return standalone_code


def _parse_assistant_content(content: str) -> tuple[str | None, str | None]:
"""Parse assistant message content for explanation and code."""
try:
Expand Down
9 changes: 9 additions & 0 deletions src/vtk_prompt/state/initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ def initialize_state(app: Any) -> None:
app.state.toast_visible = False
app.state.toast_color = "warning"

# Monaco Editor options
app.state.editor_options = {
"readOnly": True,
"minimap": {"enabled": True},
"lineNumbers": "on",
"scrollBeyondLastLine": False,
"fontSize": 13,
}

# API configuration state
app.state.use_cloud_models = True # Toggle between cloud and local
app.state.tab_index = 0 # Tab navigation state
Expand Down
37 changes: 25 additions & 12 deletions src/vtk_prompt/ui/layout/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from trame.widgets import html
from trame.widgets import vuetify3 as vuetify
from trame_code.widgets import code # type: ignore[import-not-found]
from trame_vtk.widgets import vtk as vtk_widgets

from .conversation_history import build_conversation_history
Expand Down Expand Up @@ -104,22 +105,34 @@ def build_content(layout: Any, app: Any) -> None:
# Middle column - Generated code view
with vuetify.VCol(cols=4):
# Generated code panel
with vuetify.VCard(readonly=True, classes="h-100 mt-2"):
vuetify.VCardTitle("Generated Code")
with vuetify.VCard(readonly=True, classes="h-100"):
with vuetify.VCardTitle(classes="d-flex align-center"):
html.Span("Generated Code")
vuetify.VSpacer()
with vuetify.VTooltip(text="Download as .py", location="bottom"):
with vuetify.Template(v_slot_activator="{ props }"):
with vuetify.VBtn(
icon=True,
density="compact",
variant="text",
v_bind="props",
click=(
"trame.utils.vtk_prompt.download_generated_code(trame)"
),
disabled=("!generated_code",),
):
vuetify.VIcon("mdi-download")
with vuetify.VCardText(style="height: calc(100% - 50px);"):
vuetify.VTextarea(
v_model=("generated_code", ""),
readonly=True,
solo=True,
hide_details=True,
no_resize=True,
classes="overflow-y-auto fill-height",
style="font-family: monospace;",
placeholder="Generated VTK code will appear here...",
code.Editor(
model_value=("generated_code", ""),
language="python",
theme=("theme_mode === 'dark' ? 'vs-dark' : 'vs'",),
options=("editor_options",),
style="width: 100%; height: calc(100% - 75px);",
)

# Right column - VTK viewer and prompt
with vuetify.VCol(cols=5):
with vuetify.VCol(cols=5, classes="mb-2"):
with vuetify.VRow(no_gutters=True, classes="fill-height"):
# Top: VTK render view
with vuetify.VCard(classes="h-75 w-100"):
Expand Down
78 changes: 40 additions & 38 deletions src/vtk_prompt/ui/layout/toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,47 +19,48 @@ def build_toolbar(layout: Any, app: Any) -> None:
vuetify.VSpacer()

# Settings buttons
with vuetify.VTooltip(
text="Load or download files",
location="bottom",
with vuetify.VBtnGroup(
variant="outlined",
rounded="lg",
color="primary",
classes="mr-4",
divided=True,
):
with vuetify.Template(v_slot_activator="{ props }"):
with vuetify.VBtn(
icon=True,
v_bind="props",
click="advanced_settings_open = true; active_settings_tab = 'files';",
classes="mr-4",
color="primary",
):
vuetify.VIcon("mdi-file-cog-outline")
with vuetify.VTooltip(
text="Load or download files",
location="bottom",
):
with vuetify.Template(v_slot_activator="{ props }"):
with vuetify.VBtn(
icon=True,
v_bind="props",
click="advanced_settings_open = true; active_settings_tab = 'files';",
):
vuetify.VIcon("mdi-file-cog-outline")

with vuetify.VTooltip(
text="Change model settings",
location="bottom",
):
with vuetify.Template(v_slot_activator="{ props }"):
with vuetify.VBtn(
icon=True,
v_bind="props",
click="advanced_settings_open = true; active_settings_tab = 'model';",
classes="mr-4",
color="primary",
):
vuetify.VIcon("mdi-brain")
with vuetify.VTooltip(
text="Change model settings",
location="bottom",
):
with vuetify.Template(v_slot_activator="{ props }"):
with vuetify.VBtn(
icon=True,
v_bind="props",
click="advanced_settings_open = true; active_settings_tab = 'model';",
):
vuetify.VIcon("mdi-brain")

with vuetify.VTooltip(
text="Advanced settings",
location="bottom",
):
with vuetify.Template(v_slot_activator="{ props }"):
with vuetify.VBtn(
icon=True,
v_bind="props",
click="advanced_settings_open = true; active_settings_tab = 'advanced';",
classes="mr-4",
color="primary",
):
vuetify.VIcon("mdi-cog-outline")
with vuetify.VTooltip(
text="Advanced settings",
location="bottom",
):
with vuetify.Template(v_slot_activator="{ props }"):
with vuetify.VBtn(
icon=True,
v_bind="props",
click="advanced_settings_open = true; active_settings_tab = 'advanced';",
):
vuetify.VIcon("mdi-cog-outline")

# Theme switcher
vuetify.VSwitch(
Expand All @@ -69,4 +70,5 @@ def build_toolbar(layout: Any, app: Any) -> None:
true_value="light",
false_value="dark",
append_icon=("theme_mode === 'light' ? 'mdi-weather-sunny' : 'mdi-weather-night'",),
icon_color=("theme_mode === 'light' ? 'orange-darken-4' : 'purple-lighten-4'",),
)
35 changes: 35 additions & 0 deletions src/vtk_prompt/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
window.trame = window.trame || {};
window.trame.utils = window.trame.utils || {};

window.trame.utils.vtk_prompt = {
rules: {
json_file(obj) {
Expand All @@ -10,4 +13,36 @@ window.trame.utils.vtk_prompt = {
return true;
},
},
copy_to_clipboard: function (text) {
if (navigator && navigator.clipboard) {
navigator.clipboard.writeText(text).catch((err) => {
console.error("Failed to copy to clipboard:", err);
});
} else {
console.error("Clipboard API not available");
}
},
download_file: function (content, filename, mimeType) {
mimeType = mimeType || "text/plain";
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
},
download_generated_code: function (trame) {
trame.trigger("download_generated_code").then((code) => {
if (code) {
window.trame.utils.vtk_prompt.download_file(
code,
"vtk_generated.py",
"text/x-python",
);
}
});
},
};
10 changes: 10 additions & 0 deletions src/vtk_prompt/vtk_prompt_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,16 @@ def toggle_favorite_conversation(self, conversation_index: int) -> None:
"""Toggle favorite status for a conversation."""
conversation.toggle_favorite_conversation(self, conversation_index)

@controller.set("copy_generated_code")
def copy_generated_code(self) -> None:
"""Copy generated code to clipboard."""
conversation.copy_generated_code(self)

@trigger("download_generated_code")
def download_generated_code(self) -> str:
"""Download generated code as a .py file."""
return conversation.download_generated_code(self)

@trigger("save_conversation")
def save_conversation(self) -> str:
"""Save current conversation history as JSON string."""
Expand Down