From 431e5c63aa269c9b5836fd5915db8340294aebb6 Mon Sep 17 00:00:00 2001 From: armistace Date: Wed, 4 Jun 2025 16:56:08 +1000 Subject: [PATCH 1/2] first pass at docker run --- .gitignore | 1 + n8n_test.sh | 4 +++ requirements.txt | 1 + src/ai_generators/ollama_md_generator.py | 10 ++++++ src/main.py | 30 ++++++++++++++++ src/notifications/__init__.py | 0 src/notifications/n8n.py | 45 ++++++++++++++++++++++++ src/test_n8n.py | 16 +++++++++ 8 files changed, 107 insertions(+) create mode 100755 n8n_test.sh create mode 100644 src/notifications/__init__.py create mode 100644 src/notifications/n8n.py create mode 100644 src/test_n8n.py diff --git a/.gitignore b/.gitignore index 9ede049..876d65e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__ pyproject.toml .ropeproject generated_files/* +pyright* diff --git a/n8n_test.sh b/n8n_test.sh new file mode 100755 index 0000000..2d2d499 --- /dev/null +++ b/n8n_test.sh @@ -0,0 +1,4 @@ +export N8N_SECRET='11ECjor0@iM9CR3XxEn2GkNl' +export N8N_WEBHOOK_URL='http://192.168.178.159:5678/webhook-test/e054ae2d-95d0-4999-9cc7-6f8fa0d44c8f' + +python src/test_n8n.py diff --git a/requirements.txt b/requirements.txt index 116f45e..a508493 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ gitpython PyGithub chromadb langchain-ollama +PyJWT diff --git a/src/ai_generators/ollama_md_generator.py b/src/ai_generators/ollama_md_generator.py index c70c790..c2e430a 100644 --- a/src/ai_generators/ollama_md_generator.py +++ b/src/ai_generators/ollama_md_generator.py @@ -154,9 +154,19 @@ class OllamaGenerator: with open(filename, "w") as f: f.write(self.generate_markdown()) + # TODO: Make this generic a "create message for system" if you will + # This will allow me to control the system and human prompt at the client + # level rather than having to play within the class def generate_commit_message(self): prompt_system = "You are a blog creator commiting a piece of content to a central git repo" prompt_human = f"Generate a 5 word git commit message describing {self.response}" messages = [("system", prompt_system), ("human", prompt_human),] commit_message = self.llm.invoke(messages).text() return commit_message + + def generate_notification_summary(self): + prompt_system = "You are a blog creator notifiying the final editor of the final creation of blog" + prompt_human = f"Generate a 50 word summary describing {self.response}" + messages = [("system", prompt_system), ("human", prompt_human),] + notification_message = self.llm.invoke(messages).text() + return notification_message diff --git a/src/main.py b/src/main.py index 07817fc..9be942e 100644 --- a/src/main.py +++ b/src/main.py @@ -1,7 +1,10 @@ import ai_generators.ollama_md_generator as omg import trilium.notes as tn import repo_management.repo_manager as git_repo +from notifications.n8n import N8NWebhookJwt import string,os +from datetime import datetime + tril = tn.TrilumNotes() @@ -26,9 +29,36 @@ for note in tril_notes: tril_notes[note]['title']) blog_path = f"/blog_creator/generated_files/{os_friendly_title}.md" ai_gen.save_to_file(blog_path) + + # Generate commit messages and push to repo commit_message = ai_gen.generate_commit_message() git_user = os.environ["GIT_USER"] git_pass = os.environ["GIT_PASS"] repo_manager = git_repo.GitRepository("blog/", git_user, git_pass) repo_manager.create_copy_commit_push(blog_path, os_friendly_title, commit_message) + + # Generate notification for Matrix + notification_message = ai_gen.generate_notification_summary() + secret_key = os.environ['N8N_SECRET'] + webhook_url = os.environ['N8N_WEBHOOK_URL'] + git_branch_url = f'https://git.aridgwayweb.com/armistace/blog/src/branch/{os_friendly_tile}' + notification_string = f""" +

{tril_notes[note]['title']}

+

Summary

+

{notification_message}

+

Branch

+

{os_friendly_title}

+

Link to Branch

+ """ + + payload = { + "message": f"{notification_string}", + "timestamp": datetime.now().isoformat() + } + + webhook_client = N8NWebhookJwt(secret_key, webhook_url) + + n8n_result = webhook_client.send_webhook(payload) + + print(f"N8N response: {n8n_result}") diff --git a/src/notifications/__init__.py b/src/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/notifications/n8n.py b/src/notifications/n8n.py new file mode 100644 index 0000000..d9f4b3e --- /dev/null +++ b/src/notifications/n8n.py @@ -0,0 +1,45 @@ +from datetime import datetime, timedelta +import jwt +import requests +from typing import Dict, Optional + +class N8NWebhookJwt: + def __init__(self, secret_key: str, webhook_url: str): + self.secret_key = secret_key + self.webhook_url = webhook_url + self.token_expiration = datetime.now() + timedelta(hours=1) + + def _generate_jwt_token(self, payload: Dict) -> str: + """Generate JWT token with the given payload.""" + # Include expiration time (optional) + payload["exp"] = self.token_expiration.timestamp() + encoded_jwt = jwt.encode( + payload, + self.secret_key, + algorithm="HS256", + ) + return encoded_jwt #jwt.decode(encoded_jwt, self.secret_key, algorithms=['HS256']) + + def send_webhook(self, payload: Dict) -> Dict: + """Send a webhook request with JWT authentication.""" + # Generate JWT token + token = self._generate_jwt_token(payload) + + # Set headers with JWT token + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + } + + # Send POST request + response = requests.post( + self.webhook_url, + json=payload, + headers=headers + ) + + # Handle response + if response.status_code == 200: + return {"status": "success", "response": response.json()} + else: + return {"status": "error", "response": response.status_code, "message": response.text} diff --git a/src/test_n8n.py b/src/test_n8n.py new file mode 100644 index 0000000..3e485b8 --- /dev/null +++ b/src/test_n8n.py @@ -0,0 +1,16 @@ +import os +from datetime import datetime +from notifications.n8n import N8NWebhookJwt + +secret_key = os.environ['N8N_SECRET'] +webhook_url = os.environ['N8N_WEBHOOK_URL'] + +payload = { + "message": "Hello, N8N webhook with JWT!", + "timestamp": datetime.now().isoformat() +} + +webhook_client = N8NWebhookJwt(secret_key, webhook_url) + +result = webhook_client.send_webhook(payload) +print(result['status']) From c466b04a25220e83180cbcbb33966713d3f1868c Mon Sep 17 00:00:00 2001 From: armistace Date: Wed, 4 Jun 2025 21:32:51 +1000 Subject: [PATCH 2/2] matrix notifications and config driven chroma --- n8n_test.sh | 4 ---- src/ai_generators/ollama_md_generator.py | 28 +++++++++--------------- src/main.py | 20 +++++++++++++---- src/test_n8n.py | 16 -------------- 4 files changed, 26 insertions(+), 42 deletions(-) delete mode 100755 n8n_test.sh delete mode 100644 src/test_n8n.py diff --git a/n8n_test.sh b/n8n_test.sh deleted file mode 100755 index 2d2d499..0000000 --- a/n8n_test.sh +++ /dev/null @@ -1,4 +0,0 @@ -export N8N_SECRET='11ECjor0@iM9CR3XxEn2GkNl' -export N8N_WEBHOOK_URL='http://192.168.178.159:5678/webhook-test/e054ae2d-95d0-4999-9cc7-6f8fa0d44c8f' - -python src/test_n8n.py diff --git a/src/ai_generators/ollama_md_generator.py b/src/ai_generators/ollama_md_generator.py index c2e430a..2865b68 100644 --- a/src/ai_generators/ollama_md_generator.py +++ b/src/ai_generators/ollama_md_generator.py @@ -11,7 +11,11 @@ class OllamaGenerator: self.inner_title = inner_title self.content = content self.response = None - self.chroma = chromadb.HttpClient(host="172.18.0.2", port=8000) + try: + chroma_port = int(os.environ['CHROMA_PORT']) + except ValueError as e: + raise Exception(f"CHROMA_PORT is not an integer: {e}") + self.chroma = chromadb.HttpClient(host=os.environ['CHROMA_HOST'], port=chroma_port) ollama_url = f"{os.environ["OLLAMA_PROTOCOL"]}://{os.environ["OLLAMA_HOST"]}:{os.environ["OLLAMA_PORT"]}" self.ollama_client = Client(host=ollama_url) self.ollama_model = os.environ["EDITOR_MODEL"] @@ -20,7 +24,7 @@ class OllamaGenerator: self.llm = ChatOllama(model=self.ollama_model, temperature=0.6, top_p=0.5) #This is the level head in the room self.prompt_inject = f""" You are a journalist, Software Developer and DevOps expert - writing a 3000 word draft blog for other tech enthusiasts. + writing a 3000 word draft blog article for other tech enthusiasts. You like to use almost no code examples and prefer to talk in a light comedic tone. You are also Australian As this person write this blog as a markdown document. @@ -116,7 +120,7 @@ class OllamaGenerator: prompt_system = f""" You are an editor taking information from {len(self.agent_models)} Software Developers and Data experts - writing a 3000 word blog. You like when they use almost no code examples. + writing a 3000 word blog article. You like when they use almost no code examples. You are also Australian. The content may have light comedic elements, you are more professional and will attempt to tone these down As this person produce and an amalgamtion of this blog as a markdown document. @@ -154,19 +158,7 @@ class OllamaGenerator: with open(filename, "w") as f: f.write(self.generate_markdown()) - # TODO: Make this generic a "create message for system" if you will - # This will allow me to control the system and human prompt at the client - # level rather than having to play within the class - def generate_commit_message(self): - prompt_system = "You are a blog creator commiting a piece of content to a central git repo" - prompt_human = f"Generate a 5 word git commit message describing {self.response}" + def generate_system_message(self, prompt_system, prompt_human): messages = [("system", prompt_system), ("human", prompt_human),] - commit_message = self.llm.invoke(messages).text() - return commit_message - - def generate_notification_summary(self): - prompt_system = "You are a blog creator notifiying the final editor of the final creation of blog" - prompt_human = f"Generate a 50 word summary describing {self.response}" - messages = [("system", prompt_system), ("human", prompt_human),] - notification_message = self.llm.invoke(messages).text() - return notification_message + ai_message = self.llm.invoke(messages).text() + return ai_message diff --git a/src/main.py b/src/main.py index 9be942e..3ca9c09 100644 --- a/src/main.py +++ b/src/main.py @@ -32,17 +32,28 @@ for note in tril_notes: # Generate commit messages and push to repo - commit_message = ai_gen.generate_commit_message() + print("Generating Commit Message") + git_sytem_prompt = "You are a blog creator commiting a piece of content to a central git repo" + git_human_prompt = f"Generate a 5 word git commit message describing {ai_gen.response}. ONLY OUTPUT THE RESPONSE" + commit_message = ai_gen.generate_system_message(git_sytem_prompt, git_human_prompt) git_user = os.environ["GIT_USER"] git_pass = os.environ["GIT_PASS"] repo_manager = git_repo.GitRepository("blog/", git_user, git_pass) + print("Pushing to Repo") repo_manager.create_copy_commit_push(blog_path, os_friendly_title, commit_message) # Generate notification for Matrix - notification_message = ai_gen.generate_notification_summary() + print("Generating Notification Message") + git_branch_url = f'https://git.aridgwayweb.com/armistace/blog/src/branch/{os_friendly_title}/src/content/{os_friendly_title}.md' + n8n_system_prompt = f"You are a blog creator notifiying the final editor of the final creation of blog available at {git_branch_url}" + n8n_prompt_human = f""" + Generate an informal 150 word + summary describing {ai_gen.response}. + Don't address it or use names. ONLY OUTPUT THE RESPONSE + """ + notification_message = ai_gen.generate_system_message(n8n_system_prompt, n8n_prompt_human) secret_key = os.environ['N8N_SECRET'] webhook_url = os.environ['N8N_WEBHOOK_URL'] - git_branch_url = f'https://git.aridgwayweb.com/armistace/blog/src/branch/{os_friendly_tile}' notification_string = f"""

{tril_notes[note]['title']}

Summary

@@ -59,6 +70,7 @@ for note in tril_notes: webhook_client = N8NWebhookJwt(secret_key, webhook_url) + print("Notifying") n8n_result = webhook_client.send_webhook(payload) - print(f"N8N response: {n8n_result}") + print(f"N8N response: {n8n_result['status']}") diff --git a/src/test_n8n.py b/src/test_n8n.py deleted file mode 100644 index 3e485b8..0000000 --- a/src/test_n8n.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -from datetime import datetime -from notifications.n8n import N8NWebhookJwt - -secret_key = os.environ['N8N_SECRET'] -webhook_url = os.environ['N8N_WEBHOOK_URL'] - -payload = { - "message": "Hello, N8N webhook with JWT!", - "timestamp": datetime.now().isoformat() -} - -webhook_client = N8NWebhookJwt(secret_key, webhook_url) - -result = webhook_client.send_webhook(payload) -print(result['status'])