Skip to content

Commit

Permalink
Edit Lines (#226)
Browse files Browse the repository at this point in the history
Add option to edit speaker text
  • Loading branch information
vivekuppal committed May 30, 2024
1 parent e39df48 commit 3b3502d
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 19 deletions.
85 changes: 68 additions & 17 deletions app/transcribe/appui.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,14 @@ def create_ui_components(self, config: dict):
'https://github.com/vivekuppal/transcribe/issues/new?referer=desktop'))

# Create right click menu for transcript textbox.
# This displays only inside the speech to text textbox
m = tk.Menu(self.main_frame, tearoff=0)
m.add_command(label="Generate response for selected text",
command=self.get_response_selected_now)
m.add_command(label="Save Transcript to File", command=self.save_file)
m.add_command(label="Clear Audio Transcript", command=self.clear_transcript)
m.add_command(label="Copy Transcript to Clipboard", command=self.copy_to_clipboard)
m.add_separator()
m.add_command(label="Quit", command=self.quit)
self.transcript_text.add_right_click_menu(label="Generate response for selected text",
command=self.get_response_selected_now)
self.transcript_text.add_right_click_menu(label="Save Transcript to File", command=self.save_file)
self.transcript_text.add_right_click_menu(label="Clear Audio Transcript", command=self.clear_transcript)
self.transcript_text.add_right_click_menu(label="Copy Transcript to Clipboard", command=self.copy_to_clipboard)
self.transcript_text.add_right_click_menu(label="Edit line", command=self.edit_current_line)
self.transcript_text.add_right_menu_separator()
self.transcript_text.add_right_click_menu(label="Quit", command=self.quit)

chat_inference_provider = config['General']['chat_inference_provider']
if chat_inference_provider == 'openai':
Expand Down Expand Up @@ -226,22 +225,74 @@ def create_ui_components(self, config: dict):
delay=0.01, follow=True, parent_kwargs={"padx": 3, "pady": 3},
padx=7, pady=7)

def show_context_menu(event):
try:
m.tk_popup(event.x_root, event.y_root)
finally:
m.grab_release()

self.transcript_text.bind("<Button-3>", show_context_menu)

# self.grid_rowconfigure(0, weight=100)
# self.grid_rowconfigure(1, weight=1)
# self.grid_rowconfigure(2, weight=1)
# self.grid_rowconfigure(3, weight=1)
# self.grid_columnconfigure(0, weight=1)
# self.grid_columnconfigure(1, weight=1)


def edit_current_line(self):
"""Edit the selected line of text required
"""
try:
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")

current_line = self.transcript_text.text_widget.index("insert linestart")
current_line_text = self.transcript_text.text_widget.get(current_line, f"{current_line} lineend")

edit_window = tk.Toplevel(self)
edit_window.title("Edit Line")
edit_window.configure(background='#252422')

edit_text = tk.Text(edit_window, wrap="word", height=10, width=50,
bg='#252422', font=("Arial", 20),
foreground='#639cdc')
edit_text.pack(expand=True, fill='both')
# Separate Person, text in this line
end_speaker = current_line_text.find(':')
if end_speaker == -1:
# Could not determine speaker in text
return
speaker: str = current_line_text[:end_speaker].strip()
speaker_text: str = current_line_text[end_speaker+1:].strip()
if speaker_text[0] == '[':
speaker_text = speaker_text[1:]
if speaker_text[-1] == ']':
speaker_text = speaker_text[:-1]
edit_text.insert(tk.END, speaker_text)

def save_edit():
# Needs to do 3 things
# 1. Edit the text in SelectableText class
# 2. Edit the convo object
# 3. Save in DBs
new_text = edit_text.get("1.0", tk.END).strip()
self.transcript_text.text_widget.configure(state="normal")
self.transcript_text.text_widget.delete(current_line, f"{current_line} lineend")
self.transcript_text.text_widget.insert(current_line, f'{speaker}: {new_text}')
self.transcript_text.text_widget.configure(state="disabled")
# Separate persona, text
convo_id = self.global_vars.convo.get_convo_id(persona=speaker, input_text=speaker_text)
self.global_vars.convo.update_conversation_by_id(persona=speaker, convo_id=convo_id, text=new_text)
edit_window.destroy()

def cancel_edit():
edit_window.destroy()
save_button = ctk.CTkButton(edit_window, text="Save", command=save_edit)
save_button.pack(side=ctk.LEFT, padx=10, pady=10)

cancel_button = ctk.CTkButton(edit_window, text="Cancel", command=cancel_edit)
cancel_button.pack(side=ctk.RIGHT, padx=10, pady=10)

except tk.TclError:
pass # No text in the line

def create_menus(self):
"""Create menus for the application
"""
# Create the menu bar
menubar = tk.Menu(self)

Expand Down
56 changes: 55 additions & 1 deletion app/transcribe/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

class Conversation:
"""Encapsulates the complete conversation.
Has text from Speakers, Microphone, LLM, Instructions to LLM
The member transcript_data has separate lists for different personas.
Each list has a tuple of (ConversationText, time, conversation_id)
"""
_initialized: bool = False
update_handler = None
Expand Down Expand Up @@ -68,6 +69,34 @@ def clear_conversation_data(self):
self.transcript_data[constants.PERSONA_ASSISTANT].clear()
self.initialize_conversation()

def update_conversation_by_id(self, persona: str, convo_id: int, text: str):
"""
Update a conversation entry in the transcript_data list.
Args:
persona (str): The persona whose conversation is to be updated.
convo_id (int): The ID of the conversation entry to update.
text (str): The new content of the conversation.
"""
transcript = self.transcript_data[persona]

# Find the conversation with the given convo_id
for index, (_, time_spoken, current_convo_id) in enumerate(transcript):
if current_convo_id == convo_id:
# Update the conversation text
new_convo_text = f"{persona}: [{text}]\n\n"
transcript[index] = (new_convo_text, time_spoken, convo_id)
# Update the conversation in the database
if self._initialized:
# inv_id = appdb().get_invocation_id()
convo_object: convodb.Conversations = appdb().get_object(convodb.TABLE_NAME)
convo_object.update_conversation(convo_id, text)
# if persona.lower() != 'assistant':
# self.update_handler(persona, new_convo_text)
break
else:
print(f'Conversation with ID {convo_id} not found for persona {persona}.')

def update_conversation(self, persona: str,
text: str,
time_spoken,
Expand Down Expand Up @@ -123,6 +152,31 @@ def update_conversation(self, persona: str,

self.last_update = datetime.datetime.utcnow()

def get_convo_id(self, persona: str, input_text: str):
"""
Retrieves the ID of the conversation row that matches the given speaker and text.
Args:
speaker (str): The name of the speaker.
text (str): The content of the conversation.
Returns:
int: The ID of the matching conversation entry.
"""
if not self._initialized:
return
cleaned_text = input_text.strip()
if cleaned_text[0] == '[':
cleaned_text = cleaned_text[1:]
if cleaned_text[-1] == ']':
cleaned_text = cleaned_text[:-1]
inv_id = appdb().get_invocation_id()
convo_object: convodb.Conversations = appdb().get_object(convodb.TABLE_NAME)
convo_id = convo_object.get_convo_id_by_speaker_and_text(speaker=persona,
input_text=cleaned_text,
inv_id=inv_id)
return convo_id

def on_convo_select(self, input_text: str):
"""Callback when a specific conversation is selected.
"""
Expand Down
19 changes: 19 additions & 0 deletions app/transcribe/db/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,25 @@ def get_max_convo_id(self, speaker: str, inv_id: int) -> int:

return convo_id

def get_convo_id_by_speaker_and_text(self, speaker: str, input_text: str, inv_id: int) -> int:
"""
Retrieves the ID of the conversation row that matches the given speaker and text.
Args:
speaker (str): The name of the speaker.
text (str): The content of the conversation.
Returns:
int: The ID of the matching conversation entry.
"""
stmt = text(f'SELECT Id FROM {self._table_name} WHERE Speaker = :speaker and Text = :text and InvocationId = :inv_id')
with Session(self.engine) as session:
result = session.execute(stmt, {'speaker': speaker, 'text': input_text, 'inv_id': inv_id})
convo_id = result.scalar()
session.commit()

return convo_id

def update_conversation(self, conversation_id: int, convo_text: str):
"""
Updates the text of a conversation entry in the Conversations table.
Expand Down
36 changes: 35 additions & 1 deletion app/transcribe/uicomp/selectable_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,17 @@
Get the text of the last 3 rows.
"""

from tkinter import Text, Scrollbar, END
from tkinter import Text, Scrollbar, END, Menu, TclError
import customtkinter as ctk


class SelectableText(ctk.CTkFrame):
"""Custom TKinter Component to display multiple lines of text
and support custom functionality on clicking a line of text.
"""

context_menu = None

def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)

Expand All @@ -93,6 +96,37 @@ def __init__(self, master=None, **kwargs):
self.text_widget.tag_configure("highlight", background="white")
self.on_text_click_cb = None

# Bind right click menu
self.text_widget.bind("<Button-3>", self.show_context_menu)

def add_right_click_menu(self, label, command):
"""Add an entry to the right click menu.
"""
if not self.context_menu:
self.context_menu = Menu(self, tearoff=0)
self.context_menu.add_command(label=label, command=command)

def get_selected_text(self):
self.text_widget.selection_get()

def add_right_menu_separator(self):
"""Add a separator to right click menu.
"""
self.context_menu.add_separator()

def show_context_menu(self, event):
"""Show the context menu.
"""
if self.context_menu:
self.context_menu.post(event.x_root, event.y_root)

def copy_text(self):
try:
self.clipboard_clear()
self.clipboard_append(self.selection_get())
except TclError:
pass # No text selected

def set_callbacks(self, onTextClick):
"""Set callback handlers
"""
Expand Down

0 comments on commit 3b3502d

Please sign in to comment.