Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ac54ff4
fixed ruff errors
hafiz-ahtasham-ali Dec 2, 2025
62426f5
moved prompt to prompts.py
hafiz-ahtasham-ali Dec 2, 2025
3c86052
use env var for FLASK_ENV with dev fallback (we'll set env value only…
hafiz-ahtasham-ali Dec 2, 2025
b47755c
add button for sqlite-web in emulator
hafiz-ahtasham-ali Dec 2, 2025
14d0fa8
fixed empty resposne issue
hafiz-ahtasham-ali Dec 3, 2025
e55d9f7
format to inline code
hafiz-ahtasham-ali Dec 3, 2025
45f1999
update database btn placement
hafiz-ahtasham-ali Dec 3, 2025
69f1bbd
db: enable autocommit (isolation_level=None) to fix data persistence
hafiz-ahtasham-ali Dec 3, 2025
00bf939
Enable audio processing status updates in emulator and fix raw JSON r…
hafiz-ahtasham-ali Dec 3, 2025
6dd310d
fixed receiving transcript files
hafiz-ahtasham-ali Dec 3, 2025
bdc0530
fix(emulator): switch log streaming to pub/sub to resolve missing fir…
hafiz-ahtasham-ali Dec 3, 2025
f3dcca6
update sender bubble color in emulator
hafiz-ahtasham-ali Dec 3, 2025
82156ab
feat(emulator): add audio volume visualizer to recording interface
hafiz-ahtasham-ali Dec 3, 2025
4655e0c
feat(emulator): add audio playback controls for recorded messages
hafiz-ahtasham-ali Dec 3, 2025
a9449fa
refactor: centralize user-facing messages and fix ruff linter errors
hafiz-ahtasham-ali Dec 3, 2025
47a6064
feat(emulator): replace preset dropdown with scrollable list of split…
hafiz-ahtasham-ali Dec 3, 2025
84da1c0
ci: add feature branch workflow and fix test stability (#30)
hafiz-ahtasham-ali Dec 4, 2025
3e25352
ci: separate workflows for push and pull request events (#32)
hafiz-ahtasham-ali Dec 4, 2025
4e15677
ci: add dev deployment workflow (#37)
hafiz-ahtasham-ali Dec 5, 2025
21b1d53
update autodeploy action commands (#38)
hafiz-ahtasham-ali Dec 5, 2025
ec2dd33
isolate dev deployment resources and update sqlite web url (#39)
hafiz-ahtasham-ali Dec 6, 2025
6891a48
updated .github\workflows\deploy-dev.yml (#40)
hafiz-ahtasham-ali Dec 6, 2025
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
32 changes: 32 additions & 0 deletions .github/workflows/ci-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI (Pull Requests)

on:
pull_request:
branches:
- dev
- main

jobs:
ci:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: uv sync --extra dev

- name: Run Ruff lint
run: uv run ruff check .

- name: Run PyTest
run: uv run pytest --cov=src/loglife/app --cov-report=term-missing
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI (Feature Branches)

on:
push:
branches:
- "feature/**" # Run CI only for feature branches

jobs:
ci:
if: github.event.pull_request == '' # ❗ RUN ONLY if this push is NOT part of a PR
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: uv sync --extra dev

- name: Run Ruff lint
run: uv run ruff check .

- name: Run PyTest
run: uv run pytest --cov=src/loglife/app --cov-report=term-missing
46 changes: 46 additions & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Deploy to Server (Dev)

on:
push:
branches: [ "main", "dev" ]

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v3

- name: Setup SSH
uses: webfactory/ssh-agent@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

# =========================
# 🚀 DEPLOY STAGING (DEV)
# =========================
- name: Deploy to DEV Server
if: github.ref == 'refs/heads/dev'
run: |
ssh -o StrictHostKeyChecking=no ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} "
# =========== PULL LATEST CODE ===========
cd /home/ali/loglife-dev &&
git pull &&

# =========== RESTART BACKEND SERVICES ===========
sudo systemctl restart loglife-dev &&
sudo systemctl restart loglife-db-dev.service &&

# =========== DEPLOY WHATSAPP CLIENT ===========
cd /home/ali/loglife-dev/whatsapp-client &&
npm ci --only=production &&
screen -S loglife-dev -X quit || true &&
screen -S loglife-dev -dm node index.js &&

# =========== BUILD MKDOCS & DEPLOY DOCS ===========
cd /home/ali/loglife-dev &&
uv run mkdocs build &&
sudo cp -r /home/ali/loglife-dev/site/* /var/www/docs.loglife.co/ &&
sudo systemctl reload nginx
"
2 changes: 1 addition & 1 deletion src/loglife/app/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
WELCOME_MESSAGE,
)
from .paths import ACCESS_LOG, DATABASE_FILE, ERROR_LOG, LOGS, SCHEMA_FILE
from .prompts import OPENAI_SUMMARIZATION_SYSTEM_PROMPT
from .secrets import ASSEMBLYAI_API_KEY, OPENAI_API_KEY
from .settings import (
COMMAND_ALIASES,
DEFAULT_GOAL_EMOJI,
FLASK_ENV,
OPENAI_CHAT_MODEL,
OPENAI_SUMMARIZATION_SYSTEM_PROMPT,
STYLE,
)

Expand Down
84 changes: 54 additions & 30 deletions src/loglife/app/config/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,36 @@
# -----------------------------
# Help Messages
# -----------------------------
HELP_MESSAGE = """```LogLife Commands:
HELP_MESSAGE = """❓ *LogLife Commands*

📋 GOALS
• goals - Show your personal goals
• add goal 😴 Description - Add new goal
• enable journaling - Quick add journaling goal
• delete [number] - Delete a goal
• update [number] [time] - Update reminder time
📋 *GOALS*
`goals` - Show your personal goals
`add goal 😴 Description` - Add new goal
`enable journaling` - Quick add journaling goal
`delete [number]` - Delete a goal
`update [number] [time]` - Update reminder time

📊 TRACKING
• rate 2 3 - Rate goal #2 with rating 3 (1=fail, 2=partial, 3=success)
• 31232 - Rate all goals at once
📊 *TRACKING*
`rate 2 3` - Rate goal #2 with rating 3 (1=fail, 2=partial, 3=success)
`31232` - Rate all goals at once

📈 VIEWING
• week - Show week summary
• lookback 7 - Show last 7 days (or any number)
📈 *VIEWING*
`week` - Show week summary
`lookback 7` - Show last 7 days (or any number)

⚙️ SETTINGS
• on transcript - Get text files with audio transcripts
• off transcript - Only get summary (no files)
⚙️ *SETTINGS*
`on transcript` - Get text files with audio transcripts
`off transcript` - Only get summary (no files)

❓ HELP
• help - Show this help message
*HELP*
`help` - Show this help message

Examples:
• add goal 🏃 Exercise daily
• rate 1 3 (rate first goal as success)
• lookback 3 (show last 3 days)
• delete 2 (delete goal #2)
• update 1 8pm (change goal #1 reminder to 8pm)```"""
*Examples:*
`add goal 🏃 Exercise daily`
`rate 1 3` (rate first goal as success)
`lookback 3` (show last 3 days)
`delete 2` (delete goal #2)
`update 1 8pm` (change goal #1 reminder to 8pm)"""

# -----------------------------
# Referral Messages
Expand All @@ -89,11 +89,11 @@
# -----------------------------
# Error Messages
# -----------------------------
ERROR_NO_GOALS_SET = "❌ You don't have any goals yet. Add one with 'add goal 😴 Description'"
ERROR_NO_GOALS_SET = "❌ You don't have any goals yet. Add one with `add goal 😴 Description`"
ERROR_INVALID_INPUT_LENGTH = "❌ Invalid input. Send <num_goals> digits."
ERROR_INVALID_GOAL_NUMBER = "Invalid goal number. Type 'goals' to see your goals."
ERROR_INVALID_DELETE_FORMAT = "Invalid format. Usage: delete [goal number]\nExample: delete 1"
ERROR_INVALID_UPDATE_FORMAT = "Usage: update [goal number] [time]\nExample: update 1 8pm"
ERROR_INVALID_GOAL_NUMBER = "Invalid goal number. Type `goals` to see your goals."
ERROR_INVALID_DELETE_FORMAT = "Invalid format. Usage: `delete [goal number]`\nExample: `delete 1`"
ERROR_INVALID_UPDATE_FORMAT = "Usage: `update [goal number] [time]`\nExample: `update 1 8pm`"
ERROR_INVALID_TIME_FORMAT = "Invalid time format. Try: 8pm, 9:30am, 20:00"
ERROR_ADD_GOAL_FIRST = "Please add a goal first."

Expand All @@ -106,7 +106,7 @@
SUCCESS_RATINGS_SUBMITTED = "📅 <today_display>\n<goal_emojis> <goal_description>: <status>"
SUCCESS_INDIVIDUAL_RATING = "📅 <today_display>\n<goal_emoji> <goal_description>: <status_symbol>"
SUCCESS_GOAL_ADDED = "Goal Added successfully! When you would like to be reminded?"
SUCCESS_JOURNALING_ENABLED = "✅ You already have a journaling goal! Check 'goals' to see it."
SUCCESS_JOURNALING_ENABLED = "✅ You already have a journaling goal! Check `goals` to see it."
SUCCESS_GOAL_DELETED = "✅ Goal deleted: {goal_emoji} {goal_description}"
SUCCESS_REMINDER_UPDATED = (
"✅ Reminder updated! I'll remind you at {display_time} for {goal_emoji} {goal_desc}"
Expand All @@ -121,7 +121,7 @@
# -----------------------------
# Lookback Summary Messages
# -----------------------------
LOOKBACK_NO_GOALS = "```No goals set. Use 'add goal 😴 Description' to add goals.```"
LOOKBACK_NO_GOALS = "No goals set. Use `add goal 😴 Description` to add goals."

# -----------------------------
# Reminder Messages
Expand Down Expand Up @@ -149,3 +149,27 @@
<goals_not_tracked_today>

You can reply with a voice note. 💭"""


# -------------------
# New Centralized Messages
# -------------------

# From handlers.py
ERROR_GOAL_NOT_FOUND = "Goal not found."
SUCCESS_REMINDER_SET = (
"Got it! I'll remind you daily at {display_time} for {goal_emoji} {goal_desc}."
)
GOALS_LIST_TIPS = (
"\n\n💡 _Tips:_\n"
"_Update reminders with `update [goal#] [time]`_\n"
"_Delete goals with `delete [goal#]`_"
)
ERROR_INVALID_TRANSCRIPT_CMD = "Invalid command. Usage: `transcript [on|off]`"

# From processor.py
ERROR_TEXT_PROCESSOR = "Error in text processor: {exc}"
ERROR_WRONG_COMMAND = "Wrong command!"

# From services/reminder/worker.py
REMINDER_UNTRACKED_HEADER = "- *Did you complete the goals?*\n"
11 changes: 11 additions & 0 deletions src/loglife/app/config/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Prompts for LLM interactions."""

OPENAI_SUMMARIZATION_SYSTEM_PROMPT = (
"You are a compassionate journal summarization assistant. "
"Your task is to create concise, meaningful summaries of users' audio "
"journal entries. Focus on key events, emotions, achievements, challenges, "
"and any goals or action items mentioned. Keep the summary in first person "
"perspective, maintain a supportive tone, and highlight important insights. "
"Aim for 2-4 sentences that capture the essence of the entry."
)

19 changes: 9 additions & 10 deletions src/loglife/app/config/settings.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
"""Application settings and configuration constants."""

FLASK_ENV = "development" # development or production
import os

OPENAI_CHAT_MODEL = "gpt-5.1"
FLASK_ENV = os.getenv("FLASK_ENV", "development") # development or production

OPENAI_SUMMARIZATION_SYSTEM_PROMPT = (
"You are a compassionate journal summarization assistant. "
"Your task is to create concise, meaningful summaries of users' audio "
"journal entries. Focus on key events, emotions, achievements, challenges, "
"and any goals or action items mentioned. Keep the summary in first person "
"perspective, maintain a supportive tone, and highlight important insights. "
"Aim for 2-4 sentences that capture the essence of the entry."
)
OPENAI_CHAT_MODEL = "gpt-5.1"

DEFAULT_GOAL_EMOJI = "🎯"

Expand All @@ -24,3 +17,9 @@
COMMAND_ALIASES = {
"journal now": "journal prompts",
}

SQLITE_WEB_URL = (
"https://test.loglife.co/database/"
if FLASK_ENV == "production"
else "http://127.0.0.1:8080/"
)
6 changes: 5 additions & 1 deletion src/loglife/app/db/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ def __init__(self, db_path: Path = DATABASE_FILE) -> None:
def conn(self) -> sqlite3.Connection:
"""Lazy loading of the database connection."""
if self._conn is None:
self._conn = sqlite3.connect(self.db_path, check_same_thread=False)
self._conn = sqlite3.connect(
self.db_path,
check_same_thread=False,
isolation_level=None,
)
self._conn.row_factory = sqlite3.Row
# Enforce foreign keys
self._conn.execute("PRAGMA foreign_keys = ON")
Expand Down
6 changes: 5 additions & 1 deletion src/loglife/app/db/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ def connect() -> sqlite3.Connection:
objects (so you can access columns by name), and hands back that
ready-to-use connection.
"""
conn: sqlite3.Connection = sqlite3.connect(DATABASE_FILE, check_same_thread=False)
conn: sqlite3.Connection = sqlite3.connect(
DATABASE_FILE,
check_same_thread=False,
isolation_level=None,
)
conn.row_factory = sqlite3.Row
return conn

Expand Down
22 changes: 18 additions & 4 deletions src/loglife/app/logic/audio/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@
logger = logging.getLogger(__name__)


def process_audio(sender: str, user: User, audio_data: str) -> str | tuple[str, str]:
def process_audio(
sender: str,
user: User,
audio_data: str,
client_type: str = "whatsapp",
) -> str | tuple[str, str]:
"""Process an incoming audio message from a user.

Arguments:
sender: The WhatsApp phone number of the sender
user: The user record dictionary
audio_data: Base64 encoded audio payload
client_type: The client type to send responses to (default: "whatsapp")

Returns:
The summarized text generated from the audio, or a tuple of
(transcript_file_base64, summarized_text).

"""
queue_async_message(sender, "Audio received. Transcribing...", client_type="whatsapp")
queue_async_message(sender, "Audio received. Transcribing...", client_type=client_type)

try:
try:
Expand All @@ -41,7 +47,11 @@ def process_audio(sender: str, user: User, audio_data: str) -> str | tuple[str,
if not transcript.strip():
return "Transcription was empty."

queue_async_message(sender, "Audio transcribed. Summarizing...", client_type="whatsapp")
queue_async_message(
sender,
"Audio transcribed. Summarizing...",
client_type=client_type,
)

try:
summary: str = summarize_transcript(transcript)
Expand All @@ -54,7 +64,11 @@ def process_audio(sender: str, user: User, audio_data: str) -> str | tuple[str,
transcription_text=transcript,
summary_text=summary,
)
queue_async_message(sender, "Summary stored in Database.", client_type="whatsapp")
queue_async_message(
sender,
"Summary stored in Database.",
client_type=client_type,
)

if user.send_transcript_file:
transcript_file: str = transcript_to_base64(transcript)
Expand Down
8 changes: 6 additions & 2 deletions src/loglife/app/logic/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
based on the message type.
"""


import logging
from typing import Any

Expand Down Expand Up @@ -33,7 +32,12 @@ def route_message(message: Message) -> None:
if message.msg_type == "chat":
response = process_text(user, message.raw_payload)
elif message.msg_type in {"audio", "ptt"}:
audio_response = process_audio(message.sender, user, message.raw_payload)
audio_response = process_audio(
message.sender,
user,
message.raw_payload,
client_type=message.client_type or "whatsapp",
)
if isinstance(audio_response, tuple):
transcript_file, response = audio_response
attachments = {"transcript_file": transcript_file}
Expand Down
Loading
Loading