Skip to content
Closed
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: 1 addition & 1 deletion src/vtk_prompt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def main(
top_k=top_k,
rag=rag,
retry_attempts=retry_attempts,
provider=provider,
_provider=provider,
custom_prompt=custom_prompt_data,
)

Expand Down
32 changes: 31 additions & 1 deletion src/vtk_prompt/controllers/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,36 @@ def navigate_conversation_right(app: Any) -> None:
_update_navigation_state(app)


def navigate_to_conversation(app: Any, target_index: int) -> None:
"""Navigate directly to a specific conversation pair by index."""
if not app.state.conversation_navigation:
return

nav_length = len(app.state.conversation_navigation)
if target_index < 0 or target_index >= nav_length:
return

app.state.conversation_index = target_index
_process_conversation_pair(app, target_index)
_update_navigation_state(app)


def toggle_favorite_conversation(app: Any, conversation_index: int) -> None:
"""Toggle favorite status for a conversation by index."""
if not hasattr(app.state, "favorited_conversations"):
app.state.favorited_conversations = []

current_favorites = app.state.favorited_conversations[:]

if conversation_index in current_favorites:
current_favorites.remove(conversation_index)
else:
current_favorites.append(conversation_index)

# Force reactivity by replacing the entire array
app.state.favorited_conversations = current_favorites


def save_conversation(app: Any) -> str:
"""Save current conversation history as JSON string."""
if hasattr(app, "prompt_client") and app.prompt_client is not None:
Expand Down Expand Up @@ -183,7 +213,7 @@ def _process_conversation_pair(app: Any, pair_index: int | None = None) -> None:
else:
query_text = user_content

app.state.query_text = query_text
return query_text


def _process_loaded_conversation(
Expand Down
3 changes: 3 additions & 0 deletions src/vtk_prompt/state/initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def initialize_state(app: Any) -> None:
app.state.can_navigate_left = False
app.state.can_navigate_right = False
app.state.is_viewing_history = False
app.state.history_sort_order = "newest" # Conversation history sort order
app.state.favorited_conversations = [] # List of favorited conversation indices
app.state.history_filter_mode = "all" # Filter mode: "all" or "favorites"

# Prompt file state variables
app.state.prompt_object = None
Expand Down
48 changes: 11 additions & 37 deletions src/vtk_prompt/ui/layout/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from trame.widgets import vuetify3 as vuetify
from trame_vtk.widgets import vtk as vtk_widgets

from .conversation_history import build_conversation_history


def build_content(layout: Any, app: Any) -> None:
"""Build the main content area with code panels and VTK viewer."""
Expand All @@ -19,8 +21,8 @@ def build_content(layout: Any, app: Any) -> None:
classes="fluid fill-height", style="min-width: 100%; padding: 0!important;"
):
with vuetify.VRow(rows=12, classes="fill-height px-4 pt-1 pb-1"):
# Left column - Generated code view
with vuetify.VCol(cols=6):
# Left column - Prompt and conversation history
with vuetify.VCol(cols=3, classes="fill-height"):
# Prompt input
with vuetify.VCard(classes="h-25"):
with vuetify.VCardText(classes="h-100"):
Expand Down Expand Up @@ -64,15 +66,6 @@ def build_content(layout: Any, app: Any) -> None:
)

with html.Div(classes="d-flex", style="height: calc(100% - 75px);"):
with vuetify.VBtn(
variant="tonal",
icon=True,
rounded="0",
disabled=("!can_navigate_left",),
classes="h-auto mr-1",
click=app.ctrl.navigate_conversation_left,
):
vuetify.VIcon("mdi-arrow-left-circle")
# Query input
vuetify.VTextarea(
label="Describe VTK visualization",
Expand All @@ -83,30 +76,6 @@ def build_content(layout: Any, app: Any) -> None:
hide_details=True,
no_resize=True,
)
with vuetify.VBtn(
color=(
"conversation_index ==="
+ " conversation_navigation.length - 1"
+ " ? 'success' : 'default'",
"default",
),
variant="tonal",
icon=True,
rounded="0",
disabled=("!can_navigate_right",),
click=app.ctrl.navigate_conversation_right,
):
vuetify.VIcon(
"mdi-arrow-right-circle",
v_show="conversation_index <"
+ " conversation_navigation.length - 1",
)
vuetify.VIcon(
"mdi-message-plus",
v_show="conversation_index ==="
+ " conversation_navigation.length - 1",
)

# Generate button
vuetify.VBtn(
"Generate Code",
Expand All @@ -129,8 +98,13 @@ def build_content(layout: Any, app: Any) -> None:
v_show="use_cloud_models && !api_token.trim()",
)

# Bottom: Conversation History
build_conversation_history(app)

# Middle column - Generated code view
with vuetify.VCol(cols=4):
# Generated code panel
with vuetify.VCard(readonly=True, classes="h-75 mt-2"):
with vuetify.VCard(readonly=True, classes="h-100 mt-2"):
vuetify.VCardTitle("Generated Code")
with vuetify.VCardText(style="height: calc(100% - 50px);"):
vuetify.VTextarea(
Expand All @@ -145,7 +119,7 @@ def build_content(layout: Any, app: Any) -> None:
)

# Right column - VTK viewer and prompt
with vuetify.VCol(cols=6):
with vuetify.VCol(cols=5):
with vuetify.VRow(no_gutters=True, classes="fill-height"):
# Top: VTK render view
with vuetify.VCard(classes="h-75 w-100"):
Expand Down
135 changes: 135 additions & 0 deletions src/vtk_prompt/ui/layout/conversation_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024, The PyTK developers.
# Distributed under a BSD-3-Clause license.
# See the LICENSE file for details.

"""Conversation History Component for VTK Prompt UI."""

from typing import Any

from trame.widgets import html
from trame.widgets import vuetify3 as vuetify


def build_conversation_history(app: Any) -> None:
"""Build the conversation history component with clickable conversation cards."""
# Bottom: Conversation History
with vuetify.VCard(classes="h-75 w-100 mt-2"):
with vuetify.VCardTitle("Conversation History", classes="d-flex align-center"):
vuetify.VSpacer()

# Sort toggle button
with vuetify.VTooltip(text="Toggle sort order", location="bottom"):
with vuetify.Template(v_slot_activator="{ props }"):
vuetify.VBtn(
icon=(
"history_sort_order === 'newest'"
+ " ? 'mdi-sort-descending' : 'mdi-sort-ascending'",
"mdi-sort-descending",
),
click=(
"history_sort_order = "
+ "(history_sort_order === 'newest')"
+ " ? 'oldest' : 'newest'"
),
variant="text",
density="compact",
color="primary",
disabled=("conversation_navigation.length === 0", False),
v_bind="props",
)

# Filter toggle button
with vuetify.VTooltip(text="Toggle favorites filter", location="bottom"):
with vuetify.Template(v_slot_activator="{ props }"):
vuetify.VBtn(
icon=(
"history_filter_mode === 'favorites'"
+ " ? 'mdi-heart' : 'mdi-heart-off'",
"mdi-format-list-bulleted",
),
click=(
"history_filter_mode = (history_filter_mode === 'all')"
+ " ? 'favorites' : 'all'"
),
variant="text",
density="compact",
color=(
"history_filter_mode === 'favorites' ? 'red' : 'primary'",
"primary",
),
disabled=(
"conversation_navigation.length === 0"
+ " || favorited_conversations.length === 0",
False,
),
v_bind="props",
)

with vuetify.VCardText(style="height: calc(100% - 50px); overflow-y: auto;"):
# Show message when no history
vuetify.VAlert(
text="No conversation history yet." + " Start by generating some VTK code!",
type="info",
variant="tonal",
v_show="conversation_navigation.length === 0",
)

# Conversation history list
with vuetify.VCard(
v_for=(
"item in (history_sort_order === 'newest'"
+ " ? conversation_navigation.slice().reverse()"
+ ".map((pair, idx) => ({pair, originalIndex: "
+ "conversation_navigation.length - 1 - idx}))"
+ " : conversation_navigation.map((pair, idx) => "
+ "({pair, originalIndex: idx})))"
+ ".filter(item => history_filter_mode === 'all' || "
+ "favorited_conversations.includes(item.originalIndex))"
),
key="item.originalIndex",
density="compact",
v_show="conversation_navigation.length > 0",
color=(
"conversation_index === item.originalIndex" + " ? 'primary' : 'secondary'",
"secondary",
),
variant=(
"conversation_index === item.originalIndex" + " ? 'outlined' : 'default'",
"default",
),
):
# Track favorited prompts
with vuetify.VCardTitle(classes="text-end"):
vuetify.VIcon(
click=(
app.ctrl.toggle_favorite_conversation,
"[item.originalIndex]",
),
icon=(
"favorited_conversations.includes(item.originalIndex) ? "
+ "'mdi-heart' : 'mdi-heart-outline'",
"mdi-heart-outline",
),
size="small",
color=(
"favorited_conversations.includes(item.originalIndex) ? "
+ "'red' : 'grey'",
"grey",
),
)
with vuetify.VCardText(
click=(
app.ctrl.navigate_to_conversation,
"[item.originalIndex]",
),
rounded=True,
classes="mb-2",
):
# User query preview
html.Span(
"{{ (item.pair.user.content.includes('</extra_instructions>')"
+ " ? item.pair.user.content.split('</extra_instructions>')[1]"
+ " : item.pair.user.content)"
+ ".trim().replace(/^Request:\\s*/i, '') }}"
)
15 changes: 14 additions & 1 deletion src/vtk_prompt/vtk_prompt_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@ def navigate_conversation_right(self) -> None:
"""Navigate to next conversation pair."""
conversation.navigate_conversation_right(self)

@controller.set("navigate_to_conversation")
def navigate_to_conversation(self, target_index: int) -> None:
"""Navigate directly to a specific conversation pair."""
conversation.navigate_to_conversation(self, target_index)

@controller.set("toggle_favorite_conversation")
def toggle_favorite_conversation(self, conversation_index: int) -> None:
"""Toggle favorite status for a conversation."""
conversation.toggle_favorite_conversation(self, conversation_index)

@trigger("save_conversation")
def save_conversation(self) -> str:
"""Save current conversation history as JSON string."""
Expand All @@ -224,7 +234,7 @@ def _build_ui(self) -> None:
self.state.main_drawer = False

with SinglePageLayout(
self.server, theme=("theme_mode", "light"), style="max-height: 100vh;"
self.server, theme=("theme_mode", "light"), style="max-height: 100vh; overflow: hidden;"
) as layout:
layout.title.set_text("VTK Prompt UI")

Expand All @@ -233,6 +243,9 @@ def _build_ui(self) -> None:
build_content(layout, self)
build_settings_dialog(layout, self)

with layout.footer as footer:
footer.hide()

def start(self) -> None:
"""Start the trame server."""
self.server.start()
Expand Down