Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev/enhancements #23

Merged
merged 6 commits into from
Aug 10, 2024
Merged
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
7 changes: 6 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ simplejson = "*"
attrs = "*"
certifi = "*"
requests-file = "*"
#textual = { version = "==0.74.0", extras = ["syntax"] }
#textual = { version = "==0.75.0", extras = ["syntax"] }
textual = { version = "*", extras = ["syntax"] }
asyncio = "*"
humanize = "*"
Expand All @@ -29,6 +29,11 @@ isodate2 = "*"
pypdf = "*"
semver = "*"
pytz = "*"
sqlalchemy = "*"
jsonschema = "*"
markdownify = "*"
tiktoken = "*"


[dev-packages]
mypy = "*"
Expand Down
578 changes: 492 additions & 86 deletions Pipfile.lock

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* [Custom Prompts](#Custom-Prompts)
* [Themes](#themes)
* [Contributing](#contributing)
* [FAQ](#faq)
* [Roadmap](#roadmap)
* [Where we are](#where-we-are)
* [Where we're going](#where-were-going)
Expand Down Expand Up @@ -295,6 +296,15 @@ pre-commit run --all-files
After running the above all future commits will auto run pre-commit. pre-commit will fix what it can and show what
if anything remains to be fixed before the commit is allowed.

## FAQ
* Q: Do I need Docker?
* A: Docker is only required if you want to Quantize models downloaded from Huggingface or similar llm repositories.
* Q: Does ParLlama require internet access?
* A: ParLlama by default does not require any network / internet access unless you enable checking for updates or want to import / use data from an online source.
* Q: Does ParLlama run on ARM?
* A: Short answer is yes. ParLlama should run any place python does. It has been tested on Windows 11 x64, Windows WSL x64, Mac OSX intel and silicon
* Q: Does Parllama require Ollama be installed locally?
* A: No. ParLlama has options to connect to remote Ollama instances

## Roadmap

Expand All @@ -307,13 +317,18 @@ if anything remains to be fixed before the commit is allowed.
* Auto complete of slash commands, input history, multi line edit

### Where we're going
* Expand ability to import custom prompts of other tools
* Chat using embeddings for local documents
* Expand ability to import custom prompts of other tools
* LLM tool use
* Ability to use other AI providers like Open AI

## What's new

### v0.3.6
* Added option to save chat input history and set its length
* Fixed tab switch issue on startup
* Added cache for Fabric import to speed up subsequent imports

### v0.3.5
* Added first time launch welcome
* Added Options tab which exposes more options than are available via command line switches
Expand Down
2 changes: 1 addition & 1 deletion parllama/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
__credits__ = ["Paul Robello"]
__maintainer__ = "Paul Robello"
__email__ = "probello@gmail.com"
__version__ = "0.3.5"
__version__ = "0.3.6"
__licence__ = "MIT"
__application_title__ = "PAR LLAMA"
__application_binary__ = "parllama"
Expand Down
8 changes: 7 additions & 1 deletion parllama/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from parllama.data_manager import dm
from parllama.dialogs.help_dialog import HelpDialog
from parllama.dialogs.information import InformationDialog
from parllama.messages.messages import ChangeTab
from parllama.messages.messages import ChangeTab, ClearChatInputHistory
from parllama.messages.messages import CreateModelFromExistingRequested
from parllama.messages.messages import DeletePrompt
from parllama.messages.messages import DeleteSession
Expand Down Expand Up @@ -684,6 +684,12 @@ async def on_model_interact_requested(self, event: ModelInteractRequested) -> No
await self.main_screen.chat_view.active_tab.action_new_session()
self.main_screen.chat_view.user_input.focus()

@on(ClearChatInputHistory)
def on_clear_chat_history(self, event: ClearChatInputHistory) -> None:
"""Clear chat history event"""
event.stop()
self.post_message_all(event)

@on(SessionListChanged)
def on_session_list_changed(self, event: SessionListChanged) -> None:
"""Session list changed event"""
Expand Down
22 changes: 19 additions & 3 deletions parllama/dialogs/import_fabric_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class ImportFabricDialog(ModalScreen[None]):
border: thick $accent;
border-title-color: $primary;
padding: 1;
&> Horizontal {
height: 3;
Button {
margin-right: 1;
}
}
&> VerticalScroll {
& > Horizontal {
height: 3;
Expand Down Expand Up @@ -69,7 +75,9 @@ def compose(self) -> ComposeResult:
"""Compose the content of the dialog."""
with Vertical() as vs:
vs.border_title = "Import Fabric"
yield Button("Import", id="import")
with Horizontal():
yield Button("Import", id="import")
yield Button("Refresh", id="refresh")

with VerticalScroll(id="prompts_container"):
yield Checkbox(label="Select All", id="select_all", value=False)
Expand All @@ -91,9 +99,9 @@ def on_mount(self) -> None:
self.fetch_patterns()

@work(thread=True)
async def fetch_patterns(self) -> None:
async def fetch_patterns(self, force: bool = False) -> None:
"""Fetch fabric patterns."""
import_fabric_manager.fetch_patterns()
import_fabric_manager.read_patterns(force)
self.post_message(ImportReady())

@on(ImportReady)
Expand Down Expand Up @@ -131,3 +139,11 @@ def on_import_pressed(self, event: Button.Pressed) -> None:

with self.prevent(Focus):
self.app.pop_screen()

@on(Button.Pressed, "#refresh")
def on_refresh_pressed(self, event: Button.Pressed) -> None:
"""Re download fabric patterns."""
event.stop()
self.loading = True
self.notify("Fetching data... This can take a few minutes.")
self.fetch_patterns(True)
5 changes: 5 additions & 0 deletions parllama/messages/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,8 @@ class ImportReady(Message):
@dataclass
class ToggleInputMode(Message):
"""Toggle between single and multi-line input mode."""


@dataclass
class ClearChatInputHistory(Message):
"""Clear chat history."""
74 changes: 49 additions & 25 deletions parllama/prompt_utils/import_fabric.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import hashlib
import os
import shutil
import tempfile
import zipfile

Expand All @@ -13,6 +14,7 @@
from parllama.chat_message import OllamaMessage
from parllama.chat_prompt import ChatPrompt
from parllama.par_event_system import ParEventSystemBase
from parllama.settings_manager import settings


class ImportFabricManager(ParEventSystemBase):
Expand All @@ -31,6 +33,7 @@ def __init__(self) -> None:
self.repo_zip_url = (
"https://github.com/danielmiessler/fabric/archive/refs/heads/main.zip"
)
self._cache_folder = os.path.join(settings.cache_dir, "fabric_prompts")
self._last_folder: str | None = None

def import_patterns(self) -> None:
Expand All @@ -43,11 +46,15 @@ def import_patterns(self) -> None:
prompt.is_dirty = True
prompt.save()

def fetch_patterns(self) -> list[ChatPrompt]:
def fetch_patterns(self, force: bool = False) -> None:
"""Create prompts from GitHub zip file."""
self.prompts.clear()
self.id_to_prompt.clear()
self.import_ids.clear()

if os.path.exists(self._cache_folder):
if force:
shutil.rmtree(self._cache_folder)
else:
return

with tempfile.TemporaryDirectory() as temp_dir:
self._last_folder = temp_dir
zip_path = os.path.join(temp_dir, "repo.zip")
Expand All @@ -61,28 +68,45 @@ def fetch_patterns(self) -> list[ChatPrompt]:
raise FileNotFoundError(
"Patterns folder not found in the downloaded zip."
)
# list all folder in patterns_source_path
pattern_folder_list = os.listdir(patterns_source_path)
for pattern_name in pattern_folder_list:
src_prompt_path = os.path.join(
patterns_source_path, pattern_name, "system.md"
shutil.copytree(patterns_source_path, self._cache_folder)

def read_patterns(self, force: bool = False) -> list[ChatPrompt]:
"""Read prompts from cache."""

self.prompts.clear()
self.id_to_prompt.clear()
self.import_ids.clear()

try:
if not os.path.exists(self._cache_folder) or force:
self.fetch_patterns(force)
except FileNotFoundError:
return []

if not os.path.exists(self._cache_folder):
return []

pattern_folder_list = os.listdir(self._cache_folder)
for pattern_name in pattern_folder_list:
src_prompt_path = os.path.join(
self._cache_folder, pattern_name, "system.md"
)
if not os.path.exists(src_prompt_path):
continue
with open(src_prompt_path, "rt", encoding="utf-8") as f:
prompt_content = ""
for line in f.readlines():
if line.upper().startswith("# INPUT") or line.upper().startswith(
"INPUT:"
):
break
prompt_content += line + "\n"
prompt_content = prompt_content.strip()
prompt: ChatPrompt = self.markdown_to_prompt(
pattern_name, prompt_content
)
if not os.path.exists(src_prompt_path):
continue
with open(src_prompt_path, "rt", encoding="utf-8") as f:
prompt_content = ""
for line in f.readlines():
if line.upper().startswith(
"# INPUT"
) or line.upper().startswith("INPUT:"):
break
prompt_content += line + "\n"
prompt_content = prompt_content.strip()
prompt: ChatPrompt = self.markdown_to_prompt(
pattern_name, prompt_content
)
self.prompts.append(prompt)
self.id_to_prompt[prompt.id] = prompt
self.prompts.append(prompt)
self.id_to_prompt[prompt.id] = prompt
return self.prompts

def markdown_to_prompt(self, pattern_name: str, prompt_content: str) -> ChatPrompt:
Expand Down
45 changes: 0 additions & 45 deletions parllama/screens/main_screen.tcss
Original file line number Diff line number Diff line change
Expand Up @@ -31,48 +31,3 @@ ListView:focus > SiteModelListItem.--highlight {
background: $surface;
}
}

ChatView {
layers: left;
SessionList {
width: 40;
height: 1fr;
dock: left;
padding: 1;
}
#chat_tabs {
height: 1fr;
}
#send_bar {
height: auto;
min-height: 3;
max-height: 15;
background: $surface-darken-1;
#send_button {
min-width: 7;
width: 7;
margin-right: 1;
}
#stop_button {
min-width: 6;
width: 6;
}
}
}

UserInput {
width: 1fr;
height: auto;
min-height: 3;
max-height: 15;
UserTextArea {
width: 1fr;
height: auto;
min-height: 3;
max-height: 15;

.text-area--cursor-line {
background: $background 0%;
}
}
}
18 changes: 18 additions & 0 deletions parllama/settings_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class Settings(BaseModel):
chat_dir: str = ""
prompt_dir: str = ""
export_md_dir: str = ""
save_chat_input_history: bool = False
chat_input_history_length: int = 100
chat_history_file: str = ""

chat_tab_max_length: int = 15
settings_file: str = "settings.json"
Expand Down Expand Up @@ -73,6 +76,7 @@ def __init__(self) -> None:
self.chat_dir = os.path.join(self.data_dir, "chats")
self.prompt_dir = os.path.join(self.data_dir, "prompts")
self.export_md_dir = os.path.join(self.data_dir, "md_exports")
self.chat_history_file = os.path.join(self.data_dir, "chat_history.json")

os.makedirs(self.cache_dir, exist_ok=True)
os.makedirs(self.chat_dir, exist_ok=True)
Expand Down Expand Up @@ -223,6 +227,13 @@ def load_from_file(self) -> None:
self.return_to_single_line_on_submit,
)

self.save_chat_input_history = data.get(
"save_chat_input_history", self.save_chat_input_history
)
self.chat_input_history_length = data.get(
"chat_input_history_length", self.chat_input_history_length
)

except FileNotFoundError:
pass # If file does not exist, continue with default settings

Expand Down Expand Up @@ -272,5 +283,12 @@ def shutting_down(self, value: bool) -> None:
"""Set whether Par Llama is shutting down"""
self._shutting_down = value

def remove_chat_history_file(self) -> None:
"""Remove the chat history file."""
try:
os.remove(self.chat_history_file)
except FileNotFoundError:
pass


settings = Settings()
Loading