Merge branch 'main' of github.com-jeffser:Jeffser/Alpaca
This commit is contained in:
commit
c95f764c77
@ -59,7 +59,7 @@ You can change anything except `$HOME` and `$OLLAMA_HOST`, to do this go to `~/.
|
|||||||
- [TylerLaBree](https://github.com/TylerLaBree) for their requests and ideas
|
- [TylerLaBree](https://github.com/TylerLaBree) for their requests and ideas
|
||||||
- [Alexkdeveloper](https://github.com/alexkdeveloper) for their help translating the app to Russian
|
- [Alexkdeveloper](https://github.com/alexkdeveloper) for their help translating the app to Russian
|
||||||
- [Imbev](https://github.com/imbev) for their reports and suggestions
|
- [Imbev](https://github.com/imbev) for their reports and suggestions
|
||||||
- [Nokse](https://github.com/Nokse22) for their contributions to the UI
|
- [Nokse](https://github.com/Nokse22) for their contributions to the UI and table rendering
|
||||||
- [Louis Chauvet-Villaret](https://github.com/loulou64490) for their suggestions and help translating the app to French
|
- [Louis Chauvet-Villaret](https://github.com/loulou64490) for their suggestions and help translating the app to French
|
||||||
- [CounterFlow64](https://github.com/CounterFlow64) for their help translating the app to Norwegian
|
- [CounterFlow64](https://github.com/CounterFlow64) for their help translating the app to Norwegian
|
||||||
|
|
||||||
|
@ -80,6 +80,21 @@
|
|||||||
<url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
|
<url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
|
||||||
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
|
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="1.0.4" date="2024-08-01">
|
||||||
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.4</url>
|
||||||
|
<description>
|
||||||
|
<p>New</p>
|
||||||
|
<ul>
|
||||||
|
<li>Added table rendering (Thanks Nokse)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fixes</p>
|
||||||
|
<ul>
|
||||||
|
<li>Made support dialog more common</li>
|
||||||
|
<li>Dialog title on tag chooser when downloading models didn't display properly</li>
|
||||||
|
<li>Prevent chat generation from generating a title with multiple lines</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="1.0.3" date="2024-08-01">
|
<release version="1.0.3" date="2024-08-01">
|
||||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.3</url>
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.3</url>
|
||||||
<description>
|
<description>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
project('Alpaca', 'c',
|
project('Alpaca', 'c',
|
||||||
version: '1.0.3',
|
version: '1.0.4',
|
||||||
meson_version: '>= 0.62.0',
|
meson_version: '>= 0.62.0',
|
||||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||||
)
|
)
|
||||||
|
1218
po/alpaca.pot
1218
po/alpaca.pot
File diff suppressed because it is too large
Load Diff
1303
po/nb_NO.po
1303
po/nb_NO.po
File diff suppressed because it is too large
Load Diff
1235
po/pt_BR.po
1235
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
1247
po/zh_CN.po
1247
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
@ -43,7 +43,8 @@ alpaca_sources = [
|
|||||||
'dialogs.py',
|
'dialogs.py',
|
||||||
'local_instance.py',
|
'local_instance.py',
|
||||||
'available_models.json',
|
'available_models.json',
|
||||||
'available_models_descriptions.py'
|
'available_models_descriptions.py',
|
||||||
|
'table_widget.py'
|
||||||
]
|
]
|
||||||
|
|
||||||
install_data(alpaca_sources, install_dir: moduledir)
|
install_data(alpaca_sources, install_dir: moduledir)
|
||||||
|
126
src/table_widget.py
Normal file
126
src/table_widget.py
Normal 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)
|
@ -28,7 +28,7 @@ from PIL import Image
|
|||||||
from pypdf import PdfReader
|
from pypdf import PdfReader
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from . import dialogs, local_instance, connection_handler, available_models_descriptions
|
from . import dialogs, local_instance, connection_handler, available_models_descriptions
|
||||||
|
from .table_widget import TableWidget
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -580,7 +580,7 @@ Generate a title following these rules:
|
|||||||
if 'images' in message: data["images"] = message['images']
|
if 'images' in message: data["images"] = message['images']
|
||||||
response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data))
|
response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data))
|
||||||
|
|
||||||
new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').title()
|
new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title()
|
||||||
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
|
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
|
||||||
self.rename_chat(label_element.get_name(), new_chat_name, label_element)
|
self.rename_chat(label_element.get_name(), new_chat_name, label_element)
|
||||||
|
|
||||||
@ -820,6 +820,16 @@ Generate a title following these rules:
|
|||||||
code_text = match.group(1)
|
code_text = match.group(1)
|
||||||
parts.append({"type": "code", "text": code_text, "language": None})
|
parts.append({"type": "code", "text": code_text, "language": None})
|
||||||
pos = end
|
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
|
# Extract any remaining normal text after the last code block
|
||||||
if pos < len(text):
|
if pos < len(text):
|
||||||
normal_text = text[pos:]
|
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')))
|
if footer: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
|
||||||
|
|
||||||
self.bot_message_box.append(message_text)
|
self.bot_message_box.append(message_text)
|
||||||
else:
|
elif part['type'] == 'code':
|
||||||
language = None
|
language = None
|
||||||
if part['language']:
|
if part['language']:
|
||||||
language = GtkSource.LanguageManager.get_default().get_language(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)
|
code_block_box.append(source_view)
|
||||||
self.bot_message_box.append(code_block_box)
|
self.bot_message_box.append(code_block_box)
|
||||||
self.style_manager.connect("notify::dark", self.on_theme_changed, buffer)
|
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 = self.chat_window.get_vadjustment()
|
||||||
vadjustment.set_value(vadjustment.get_upper())
|
vadjustment.set_value(vadjustment.get_upper())
|
||||||
self.bot_message = None
|
self.bot_message = None
|
||||||
@ -1056,7 +1069,7 @@ Generate a title following these rules:
|
|||||||
def list_available_model_tags(self, model_name):
|
def list_available_model_tags(self, model_name):
|
||||||
logger.debug("Listing available model tags")
|
logger.debug("Listing available model tags")
|
||||||
self.navigation_view_manage_models.push_by_tag('model_tags_page')
|
self.navigation_view_manage_models.push_by_tag('model_tags_page')
|
||||||
self.navigation_view_manage_models.find_page('model_tags_page').set_title(model_name.capitalize())
|
self.navigation_view_manage_models.find_page('model_tags_page').set_title(model_name.replace("-", " ").title())
|
||||||
self.model_link_button.set_name(self.available_models[model_name]['url'])
|
self.model_link_button.set_name(self.available_models[model_name]['url'])
|
||||||
self.model_link_button.set_tooltip_text(self.available_models[model_name]['url'])
|
self.model_link_button.set_tooltip_text(self.available_models[model_name]['url'])
|
||||||
self.available_model_list_box.unselect_all()
|
self.available_model_list_box.unselect_all()
|
||||||
@ -1580,7 +1593,7 @@ Generate a title following these rules:
|
|||||||
|
|
||||||
#Support dialog
|
#Support dialog
|
||||||
if 'show_support' not in data or data['show_support']:
|
if 'show_support' not in data or data['show_support']:
|
||||||
if random.randint(0, 99) == 0:
|
if random.randint(0, 49) == 0:
|
||||||
dialogs.support(self)
|
dialogs.support(self)
|
||||||
if 'show_support' in data: self.show_support = data['show_support']
|
if 'show_support' in data: self.show_support = data['show_support']
|
||||||
self.background_switch.set_active(self.run_on_background)
|
self.background_switch.set_active(self.run_on_background)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user