Added tables (#179)

This commit is contained in:
Nokse22 2024-08-01 21:44:56 +02:00 committed by GitHub
parent 55a636f4d1
commit f06c2dae23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 143 additions and 3 deletions

View File

@ -43,7 +43,8 @@ alpaca_sources = [
'dialogs.py',
'local_instance.py',
'available_models.json',
'available_models_descriptions.py'
'available_models_descriptions.py',
'table_widget.py'
]
install_data(alpaca_sources, install_dir: moduledir)

126
src/table_widget.py Normal file
View File

@ -0,0 +1,126 @@
import gi
from gi.repository import Adw
from gi.repository import Gtk, GObject, Gio
import re
class MarkdownTable:
def __init__(self):
self.headers = []
self.rows = Gio.ListStore()
self.alignments = []
def __repr__(self):
table_repr = 'Headers: {}\n'.format(self.headers)
table_repr += 'Alignments: {}\n'.format(self.alignments)
table_repr += 'Rows:\n'
for row in self.rows:
table_repr += ' | '.join(row) + '\n'
return table_repr
class Row(GObject.GObject):
def __init__(self, _values):
super().__init__()
self.values = _values
def get_column_value(self, index):
return self.values[index]
class TableWidget(Gtk.Frame):
__gtype_name__ = 'TableWidget'
def __init__(self, markdown):
super().__init__()
self.table = MarkdownTable()
self.set_halign(Gtk.Align.START)
self.table_widget = Gtk.ColumnView(
show_column_separators=True,
show_row_separators=True,
reorderable=False,
)
scrolled_window = Gtk.ScrolledWindow(
vscrollbar_policy=Gtk.PolicyType.NEVER,
propagate_natural_width=True
)
self.set_child(scrolled_window)
try:
self.parse_markdown_table(markdown)
self.make_table()
scrolled_window.set_child(self.table_widget)
except:
label = Gtk.Label(
label=markdown.lstrip('\n').rstrip('\n'),
selectable=True,
margin_top=6,
margin_bottom=6,
margin_start=6,
margin_end=6
)
scrolled_window.set_child(label)
def parse_markdown_table(self, markdown_text):
# Define regex patterns for matching the table components
header_pattern = r'^\|(.+?)\|$'
separator_pattern = r'^\|(\s*[:-]+:?\s*\|)+$'
row_pattern = r'^\|(.+?)\|$'
# Split the text into lines
lines = markdown_text.strip().split('\n')
# Extract headers
header_match = re.match(header_pattern, lines[0], re.MULTILINE)
if header_match:
headers = [header.strip() for header in header_match.group(1).replace("*", "").split('|') if header.strip()]
self.table.headers = headers
# Extract alignments
separator_match = re.match(separator_pattern, lines[1], re.MULTILINE)
if separator_match:
alignments = []
separator_columns = lines[1].replace(" ", "").split('|')[1:-1]
for sep in separator_columns:
if ':' in sep:
if sep.startswith('-') and sep.endswith(':'):
alignments.append(1)
elif sep.startswith(':') and sep.endswith('-'):
alignments.append(0)
else:
alignments.append(0.5)
else:
alignments.append(0) # Default alignment is start
self.table.alignments = alignments
# Extract rows
for line in lines[2:]:
row_match = re.match(row_pattern, line, re.MULTILINE)
if row_match:
rows = line.split('|')[1:-1]
row = Row(rows)
self.table.rows.append(row)
def make_table(self):
def _on_factory_setup(_factory, list_item, align):
label = Gtk.Label(xalign=align, ellipsize=3, selectable=True)
list_item.set_child(label)
def _on_factory_bind(_factory, list_item, index):
label_widget = list_item.get_child()
row = list_item.get_item()
label_widget.set_label(row.get_column_value(index))
for index, column_name in enumerate(self.table.headers):
column = Gtk.ColumnViewColumn(title=column_name, expand=True)
factory = Gtk.SignalListItemFactory()
factory.connect("setup", _on_factory_setup, self.table.alignments[index])
factory.connect("bind", _on_factory_bind, index)
column.set_factory(factory)
self.table_widget.append_column(column)
selection = Gtk.NoSelection.new(model=self.table.rows)
self.table_widget.set_model(model=selection)

View File

@ -28,7 +28,7 @@ from PIL import Image
from pypdf import PdfReader
from datetime import datetime
from . import dialogs, local_instance, connection_handler, available_models_descriptions
from .table_widget import TableWidget
logger = logging.getLogger(__name__)
@ -820,6 +820,16 @@ Generate a title following these rules:
code_text = match.group(1)
parts.append({"type": "code", "text": code_text, "language": None})
pos = end
# Match tables
table_pattern = re.compile(r'((\r?\n){2}|^)([^\r\n]*\|[^\r\n]*(\r?\n)?)+(?=(\r?\n){2}|$)', re.MULTILINE)
for match in table_pattern.finditer(text):
start, end = match.span()
if pos < start:
normal_text = text[pos:start]
parts.append({"type": "normal", "text": normal_text.strip()})
table_text = match.group(0)
parts.append({"type": "table", "text": table_text})
pos = end
# Extract any remaining normal text after the last code block
if pos < len(text):
normal_text = text[pos:]
@ -869,7 +879,7 @@ Generate a title following these rules:
if footer: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
self.bot_message_box.append(message_text)
else:
elif part['type'] == 'code':
language = None
if part['language']:
language = GtkSource.LanguageManager.get_default().get_language(part['language'])
@ -899,6 +909,9 @@ Generate a title following these rules:
code_block_box.append(source_view)
self.bot_message_box.append(code_block_box)
self.style_manager.connect("notify::dark", self.on_theme_changed, buffer)
elif part['type'] == 'table':
table = TableWidget(part['text'])
self.bot_message_box.append(table)
vadjustment = self.chat_window.get_vadjustment()
vadjustment.set_value(vadjustment.get_upper())
self.bot_message = None