From fcedbad1d1ef6f96962857211cce92dc43d78a9a Mon Sep 17 00:00:00 2001 From: Paulobarb Date: Tue, 24 Mar 2026 11:42:21 +0000 Subject: [PATCH 1/8] change case_sensitive to case_insesitive study_recap.py --- study_recap.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/study_recap.py b/study_recap.py index b2fc52c..735981b 100644 --- a/study_recap.py +++ b/study_recap.py @@ -129,6 +129,8 @@ def select_notes_interactively(auto_detected: list[dict]) -> list[dict]: print(f" {DIM}rm → remove a note{R}") print(f" {DIM}done → start generating{R}\n") + vault_index = {path.name.lower(): path for path in VAULT_PATH.rglob("*.md")} + while True: try: cmd = input(f"{CYAN}> {R}").strip() @@ -138,22 +140,24 @@ def select_notes_interactively(auto_detected: list[dict]) -> list[dict]: if cmd.lower() in ("done", ""): break elif cmd.startswith("add "): - filename = cmd[4:].strip() - if not filename.endswith(".md"): - filename += ".md" + target_filename = cmd[4:].strip().lower() + if not target_filename.endswith(".md"): + target_filename += ".md" + + if target_filename in vault_index: + path_obj = vault_index[target_filename] + + real_filename = path_obj.name - matches = list(VAULT_PATH.rglob(filename)) - if matches: - path_obj = matches[0] mtime = datetime.datetime.fromtimestamp(path_obj.stat().st_mtime) - note = {"name": filename, "path": path_obj, "mtime": mtime} - if filename not in [n["name"] for n in selected]: + note = {"name": real_filename, "path": path_obj, "mtime": mtime} + if real_filename not in [n["name"] for n in selected]: selected.append(note) - print(f" {GREEN}added: {filename}{R}") + print(f" {GREEN}added: {real_filename}{R}") else: print(f" {DIM}already in list{R}") else: - print(f" {RED}not found: {filename}{R}") + print(f" {RED}not found: {target_filename}{R}") elif cmd.startswith("rm "): try: idx = int(cmd[3:].strip()) - 1 From 248698e08a82e62dde95fa95df76bc3f13e9ef1b Mon Sep 17 00:00:00 2001 From: Paulobarb Date: Tue, 24 Mar 2026 11:49:18 +0000 Subject: [PATCH 2/8] standardize pathlib across ai_backend.py --- ai_backend.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ai_backend.py b/ai_backend.py index 869750c..89afba5 100644 --- a/ai_backend.py +++ b/ai_backend.py @@ -5,11 +5,13 @@ # All Ollama communication goes through this file. # ============================================================= -import os +from pathlib import Path import sys import requests -sys.path.insert(0, os.path.dirname(__file__)) +SCRIPT_DIR = Path(__file__).parent +sys.path.insert(0, str(SCRIPT_DIR)) + from config import OLLAMA_MODEL, OLLAMA_API_URL, TEMPERATURE, TIMEOUT, KEEP_ALIVE, NUM_CTX, VAULT_PATH # terminal colors @@ -53,8 +55,8 @@ def check_vault() -> bool: Returns: True if vault exists, False otherwise. """ - vault = os.path.expanduser(VAULT_PATH) - if not os.path.isdir(vault): + vault = Path(VAULT_PATH).expanduser().resolve() + if not vault.is_dir(): print(f"{RED} Error: Vault not found at: {vault}{R}") print(" Update VAULT_PATH in config.py") return False From f2fa834b5b5b5db7be82a18e2a9ff22a414da391 Mon Sep 17 00:00:00 2001 From: Paulobarb Date: Tue, 24 Mar 2026 12:16:30 +0000 Subject: [PATCH 3/8] improve week/month property label --- generate_insights.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/generate_insights.py b/generate_insights.py index 11b07b9..731ba3f 100644 --- a/generate_insights.py +++ b/generate_insights.py @@ -51,8 +51,17 @@ def fill_prompt(template: str, **kwargs) -> str: return result def get_week_label() -> str: - """Return the current ISO week number as a string, e.g. 'Week 12'.""" - return f"Week {datetime.datetime.now().isocalendar()[1]}" + """Return a human-readable week range and number, e.g. 'Mar 23 – Mar 29 (Week 13)'.""" + now = datetime.datetime.now() + + start_of_week = now - datetime.timedelta(days=now.weekday()) + + end_of_week = start_of_week + datetime.timedelta(days=6) + + range_str = f"{start_of_week.strftime('%b %d')} – {end_of_week.strftime('%b %d')}" + week_num = now.isocalendar()[1] + + return f"{range_str} (Week {week_num})" # --- PERSISTENT STATE --- @@ -253,21 +262,33 @@ def write_insight_note(lens_results: list[dict], synthesis: str, note_count: int Write the final insight note to the Insights folder in the vault. Creates the folder if it doesn't exist. """ + now = datetime.datetime.now() date_str = datetime.datetime.now().strftime("%Y-%m-%d") + + is_month = DAYS_BACK > 7 period_label = "Week" if DAYS_BACK <= 7 else "Monthly" - week_label = get_week_label() filename = f"{date_str} {period_label} Insight.md" + if is_month: + time_property = "month: " + now.strftime("%B %Y") + else: + time_property = "week: " + get_week_label() + + INSIGHT_FOLDER.mkdir(parents=True, exist_ok=True) filepath = INSIGHT_FOLDER / filename - tags = extract_tags(lens_results, synthesis) - fm_lines = ( - ["---", "creation date: " + date_str, "tags:"] - + [f" - {t}" for t in tags] - + ["week: " + week_label, "content: insights", "---", "", ""] - ) + fm_lines = [ + "---", + f"creation date: + {date_str}", + "tags:" + ] + [f" - {t}" for t in tags] + [ + time_property, + "content: insights", + "---", + "", "" + ] lines = ["## 🔮 Synthesis", "", synthesis, "", "---", ""] for r in lens_results: From eff9670e1fd1f8475fcaba9dd5cc522f970c15ab Mon Sep 17 00:00:00 2001 From: Paulobarb Date: Tue, 24 Mar 2026 12:16:49 +0000 Subject: [PATCH 4/8] fix incorrect format tags --- study_recap.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/study_recap.py b/study_recap.py index 735981b..f24a9c5 100644 --- a/study_recap.py +++ b/study_recap.py @@ -13,6 +13,7 @@ import sys import json import datetime +import re from config import EXCLUDED_FOLDERS, MAX_FILE_SIZE, VAULT_PATH, HOURS_BACK, MAX_NOTE_CHARS from ai_backend import get_backend, call_ai, backend_label, run_startup_checks @@ -38,6 +39,18 @@ RED = "\033[31m" YELLOW = "\033[33m" +def format_obsidian_tag(text: str) -> str: + """ + Limpa uma string para ser uma tag válida no Obsidian. + - Transforma em minúsculas + - Substitui espaços por hifens + - Remove tudo que não for letra, número, '-' ou '_' + """ + + text = text.lower().replace(" ", "-") + + return re.sub(r'[^a-z0-9_-]', '', text) + def fill_prompt(template: str, **kwargs) -> str: """ @@ -204,7 +217,7 @@ def write_recap(content: str, note_names: list[str]) -> str: filepath = RECAP_FOLDER / filename tags_lines = [ - f" - {Path(n).stem.lower().replace(' ', '-')}" + f" - {format_obsidian_tag(Path(n).stem)}" for n in note_names[:5] ] From af4878f3558363a48aea2afd4343f9035029b090 Mon Sep 17 00:00:00 2001 From: Paulobarb Date: Tue, 24 Mar 2026 12:24:10 +0000 Subject: [PATCH 5/8] improve title --- study_recap.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/study_recap.py b/study_recap.py index f24a9c5..d0cc4fc 100644 --- a/study_recap.py +++ b/study_recap.py @@ -207,18 +207,23 @@ def write_recap(content: str, note_names: list[str]) -> str: RECAP_FOLDER.mkdir(parents=True, exist_ok=True) date_str = datetime.datetime.now().strftime("%Y-%m-%d") - time_str = datetime.datetime.now().strftime("%H-%M") + time_str = datetime.datetime.now().strftime("%Hh%M") - base_name = Path(note_names[0]).stem if note_names else "Session" - if len(note_names) > 1: - base_name += f" +{len(note_names)-1} more" + stems = [Path(n).stem for n in note_names] + if len(stems) == 1: + subject = stems[0] + elif len(stems) == 2: + subject = f"{stems[0]} & {stems[1]}" + else: + subject = f"{stems[0]}, {stems[1]} (+{len(stems)-2})" - filename = f"{date_str} {time_str} Recap - {base_name}.md" + filename = f"{date_str} {time_str} Recap - {subject}.md" + filename = re.sub(r'[\\/*?:"<>|]', "", filename) filepath = RECAP_FOLDER / filename tags_lines = [ - f" - {format_obsidian_tag(Path(n).stem)}" - for n in note_names[:5] + f" - {format_obsidian_tag(s)}" + for s in stems[:5] ] fm_lines = ( From ec40103455ce566eb5a724bab4346b0db6c37446 Mon Sep 17 00:00:00 2001 From: Paulobarb Date: Tue, 24 Mar 2026 12:34:09 +0000 Subject: [PATCH 6/8] fix incorrect format tags --- study_recap.py | 7 ------- txt_to_notes.py | 8 ++++++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/study_recap.py b/study_recap.py index d0cc4fc..d8cefa6 100644 --- a/study_recap.py +++ b/study_recap.py @@ -40,13 +40,6 @@ YELLOW = "\033[33m" def format_obsidian_tag(text: str) -> str: - """ - Limpa uma string para ser uma tag válida no Obsidian. - - Transforma em minúsculas - - Substitui espaços por hifens - - Remove tudo que não for letra, número, '-' ou '_' - """ - text = text.lower().replace(" ", "-") return re.sub(r'[^a-z0-9_-]', '', text) diff --git a/txt_to_notes.py b/txt_to_notes.py index d075216..fc569f8 100644 --- a/txt_to_notes.py +++ b/txt_to_notes.py @@ -48,6 +48,9 @@ GREEN = "\033[32m" RED = "\033[31m" +def format_obsidian_tag(text: str) -> str: + text = text.lower().replace(" ", "-") + return re.sub(r'[^a-z0-9_-]', '', text) def fill_prompt(template: str, **kwargs) -> str: """ @@ -104,7 +107,8 @@ def collect_vault_tags() -> list[str]: tags.add(t.lower()) except Exception: continue - return sorted(tags) + cleaned_tags = {format_obsidian_tag(t) for t in tags if format_obsidian_tag(t)} + return sorted(list(cleaned_tags)) def collect_note_titles() -> list[str]: @@ -184,7 +188,7 @@ def write_note_content(text, note_plan, all_titles_in_batch, instructions, exist def write_file(note_plan, body, folder_path, date_str) -> str: """Write a single note to disk with Obsidian frontmatter.""" title = note_plan.get("title", "Untitled") - tags = [t.replace(" ", "-").lower() for t in note_plan.get("tags", [])] + tags = [format_obsidian_tag(t) for t in note_plan.get("tags", [])] safe_title = re.sub(r'[\\/*?:"<>|]', "", title) filename = f"{safe_title}.md" From 3070ab98e67fc27b146ae9e2f292ffd88cefd2c3 Mon Sep 17 00:00:00 2001 From: Paulobarb Date: Tue, 24 Mar 2026 12:39:52 +0000 Subject: [PATCH 7/8] ignore lint error --- ai_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ai_backend.py b/ai_backend.py index 89afba5..255abc6 100644 --- a/ai_backend.py +++ b/ai_backend.py @@ -12,7 +12,7 @@ SCRIPT_DIR = Path(__file__).parent sys.path.insert(0, str(SCRIPT_DIR)) -from config import OLLAMA_MODEL, OLLAMA_API_URL, TEMPERATURE, TIMEOUT, KEEP_ALIVE, NUM_CTX, VAULT_PATH +from config import OLLAMA_MODEL, OLLAMA_API_URL, TEMPERATURE, TIMEOUT, KEEP_ALIVE, NUM_CTX, VAULT_PATH # noqa: E402 # terminal colors R = "\033[0m" From d96bc58826a55419a516f67ab5ec6f68167088bb Mon Sep 17 00:00:00 2001 From: Paulobarb Date: Thu, 26 Mar 2026 12:44:17 +0000 Subject: [PATCH 8/8] change architectural to improve stability, portability, and user experience. --- ai_backend.py | 28 ++++++++++++++----------- config.py | 50 +++++++++++++++++++++++++++++++++++++++++--- generate_insights.py | 23 +++++++------------- morning_briefing.py | 15 +++++++------ study_recap.py | 13 ++++++------ txt_to_notes.py | 14 +++++++------ 6 files changed, 93 insertions(+), 50 deletions(-) diff --git a/ai_backend.py b/ai_backend.py index 255abc6..ea36a8a 100644 --- a/ai_backend.py +++ b/ai_backend.py @@ -12,7 +12,7 @@ SCRIPT_DIR = Path(__file__).parent sys.path.insert(0, str(SCRIPT_DIR)) -from config import OLLAMA_MODEL, OLLAMA_API_URL, TEMPERATURE, TIMEOUT, KEEP_ALIVE, NUM_CTX, VAULT_PATH # noqa: E402 +from config import OLLAMA_MODEL, OLLAMA_API_URL, TEMPERATURES, TIMEOUT, KEEP_ALIVE, NUM_CTX, VAULT_PATH # noqa: E402 # terminal colors R = "\033[0m" @@ -50,7 +50,7 @@ def check_ollama() -> bool: def check_vault() -> bool: """ Check if the configured vault path exists. - Prints a friendly error if not found. + Prints a error if not found. Returns: True if vault exists, False otherwise. @@ -83,7 +83,7 @@ def get_backend() -> str: return "ollama" -def call_ai(prompt: str, backend: str = "ollama", timeout: int = None) -> str: +def call_ai(prompt: str, backend: str = "ollama", timeout: int = None, temperature: float = None) -> str: """ Send a prompt to the configured AI backend and return the response text. @@ -91,24 +91,28 @@ def call_ai(prompt: str, backend: str = "ollama", timeout: int = None) -> str: prompt: The full prompt string to send to the model. backend: Which backend to use. Currently only 'ollama' is supported. timeout: Override the default timeout from config.py (in seconds). + temperature: Override the global temperature for this specific call. Returns: The model's response as a stripped string. """ - return _call_ollama(prompt, timeout or TIMEOUT) + return _call_ollama(prompt, timeout or TIMEOUT, temperature) -def _call_ollama(prompt: str, timeout: int) -> str: +def _call_ollama(prompt: str, timeout: int, temperature: float = None) -> str: """ Internal function that makes the actual HTTP request to the Ollama API. + """ - Args: - prompt: The prompt to send. - timeout: Max seconds to wait for a response. + global_temp = TEMPERATURES.get("default", 0.2) + raw_temp = temperature if temperature is not None else global_temp + + try: + final_temp = max(0.0, min(1.0, float(raw_temp))) + except (ValueError, TypeError): + print(f"\n[WARNING] Invalid temperature '{raw_temp}' in config. Defaulting to 0.2") + final_temp = 0.2 - Returns: - The model's response text, stripped of leading/trailing whitespace. - """ response = requests.post( OLLAMA_API_URL, json={ @@ -117,7 +121,7 @@ def _call_ollama(prompt: str, timeout: int) -> str: "stream": False, "keep_alive": KEEP_ALIVE, "options": { - "temperature": TEMPERATURE, + "temperature": final_temp, "top_p": 0.9, "repeat_penalty": 1.1, "num_ctx": NUM_CTX, diff --git a/config.py b/config.py index 83b47d8..4f4a98e 100644 --- a/config.py +++ b/config.py @@ -3,8 +3,21 @@ # ------------------------------------------------------------- OLLAMA_MODEL = "llama3.1:8b" # Ollama model to use -TEMPERATURE = 0.2 # Controls randomness. Lower = more factual, less hallucination. - # Range: 0.0 (deterministic) to 1.0 (creative). Default: 0.2 +# Temperature controls the randomness and creativity of the AI. +# - 0.0 to 0.3: Rigid, factual, analytical (Good for summaries/coding) +# - 0.4 to 0.7: Balanced, conversational (Good for general writing) +# - 0.8 to 1.0: Highly creative, unpredictable (Good for brainstorming) +TEMPERATURES = { + # The global fallback used if a script doesn't have a specific setting. + "default": 0.2, + + # Specific script overrides. + # Replace 'None' with a float to override the default. + "insights": None, # generate_insights.py + "briefing": None, # morning_briefing.py + "recap": None, # study_recap.py + "txt": None, # txt_to_notes.py +} # ------------------------------------------------------------- # API Configuration @@ -36,7 +49,7 @@ MAX_NOTE_CHARS = 2000 # Max characters read per note. Higher = more detail but slower. -MAX_FILE_SIZE = 1_000_000 # 1MB max per file, skip larger ones +MAX_FILE_SIZE = 1_000_000 # 1MB max per file, skip larger ones #-------------------------------------------------------------- # Ignore Folders @@ -50,3 +63,34 @@ # "Templates", # "Archive", ] + +#-------------------------------------------------------------- +# Tagging System (generate_insights.py) +#-------------------------------------------------------------- +CANDIDATES = { + "productivity": ["productiv", "task", "goal", "work", "focus"], + "mood": ["mood", "emotion", "feel", "stress", "anxiet", "happy"], + "philosophy": ["meaning", "values", "purpose", "reflect", "life"], + "habits": ["habit", "routine", "pattern", "repeat", "daily"], + "health": ["health", "sleep", "exercise", "energy", "body"], + "projects": ["project", "build", "code", "ship", "launch", "develop"], + "relationships": ["friend", "family", "partner", "social", "connect"], + # "finances": ["money", "budget", "spend", "invest", "finance"], + # Words that trigger specific tags + } + +# ------------------------------------------------------------- +# FILENAME FORMATS +# ------------------------------------------------------------- +# Customize how your generated notes are named. +# Placeholders: {date}, {period} +INSIGHT_TITLE_FORMAT = "{date} {period} Insight.md" + +# Placeholders: {date} +BRIEFING_TITLE_FORMAT = "{date} Morning Briefing.md" + +# Placeholders: {date}, {time}, {subject} +RECAP_TITLE_FORMAT = "{date} ({time}) Study Recap — {subject}.md" + +# Placeholders: {title} +TXT_TITLE_FORMAT = "{title}.md" \ No newline at end of file diff --git a/generate_insights.py b/generate_insights.py index 731ba3f..a9b5c92 100644 --- a/generate_insights.py +++ b/generate_insights.py @@ -21,7 +21,7 @@ from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completed -from config import MAX_FILE_SIZE, VAULT_PATH, DAYS_BACK, MAX_NOTE_CHARS, EXCLUDED_FOLDERS +from config import MAX_FILE_SIZE, VAULT_PATH, DAYS_BACK, MAX_NOTE_CHARS, EXCLUDED_FOLDERS, CANDIDATES, TEMPERATURES, INSIGHT_TITLE_FORMAT from ai_backend import get_backend, call_ai, backend_label, run_startup_checks SCRIPT_DIR = Path(__file__).parent @@ -39,6 +39,8 @@ GROUNDING = PROMPTS["grounding"] LENSES = PROMPTS["insights"]["lenses"] +TEMP_INSIGHTS = TEMPERATURES.get("insights") + def fill_prompt(template: str, **kwargs) -> str: """ Safe placeholder replacement that won't crash on literal { } in the template. @@ -188,7 +190,7 @@ def run_lens(lens: dict, notes_block: str, period: str, backend: str) -> dict: notes_block=notes_block, ) print(f" Running lens: {lens['name']}...") - return {"name": lens["name"], "result": call_ai(prompt, backend)} + return {"name": lens["name"], "result": call_ai(prompt, backend, temperature=TEMP_INSIGHTS)} def run_synthesis(lens_results: list[dict], period: str, state: str, backend: str) -> tuple: @@ -224,7 +226,7 @@ def run_synthesis(lens_results: list[dict], period: str, state: str, backend: st ) print(" Running final synthesis...") - raw = call_ai(prompt, backend) + raw = call_ai(prompt, backend, temperature=TEMP_INSIGHTS) return extract_state_from_synthesis(raw) @@ -238,17 +240,8 @@ def extract_tags(lens_results: list[dict], synthesis: str) -> list[str]: base_tags = ["insights"] text = synthesis.lower() + " ".join(r["result"].lower() for r in lens_results) - candidates = { - "productivity": ["productiv", "task", "goal", "work", "focus"], - "mood": ["mood", "emotion", "feel", "stress", "anxiet", "happy"], - "philosophy": ["meaning", "values", "purpose", "reflect", "life"], - "habits": ["habit", "routine", "pattern", "repeat", "daily"], - "health": ["health", "sleep", "exercise", "energy", "body"], - "projects": ["project", "build", "code", "ship", "launch", "develop"], - "relationships": ["friend", "family", "partner", "social", "connect"], - } - - for tag, keywords in candidates.items(): + + for tag, keywords in CANDIDATES.items(): if any(kw in text for kw in keywords): base_tags.append(tag) @@ -267,7 +260,7 @@ def write_insight_note(lens_results: list[dict], synthesis: str, note_count: int is_month = DAYS_BACK > 7 period_label = "Week" if DAYS_BACK <= 7 else "Monthly" - filename = f"{date_str} {period_label} Insight.md" + filename = INSIGHT_TITLE_FORMAT.format(date=date_str, period=period_label) if is_month: time_property = "month: " + now.strftime("%B %Y") diff --git a/morning_briefing.py b/morning_briefing.py index 9954cb0..3042605 100644 --- a/morning_briefing.py +++ b/morning_briefing.py @@ -16,7 +16,7 @@ import json import datetime -from config import EXCLUDED_FOLDERS, MAX_FILE_SIZE, VAULT_PATH, MAX_NOTE_CHARS +from config import EXCLUDED_FOLDERS, MAX_FILE_SIZE, VAULT_PATH, MAX_NOTE_CHARS, TEMPERATURES, BRIEFING_TITLE_FORMAT from ai_backend import get_backend, call_ai, backend_label, run_startup_checks SCRIPT_DIR = Path(__file__).parent @@ -33,6 +33,8 @@ GROUNDING = PROMPTS["grounding"] +TEMP_BRIEFING = TEMPERATURES.get("briefing") + def fill_prompt(template: str, **kwargs) -> str: """ Safe placeholder replacement that won't crash on literal { } in the template. @@ -46,8 +48,6 @@ def fill_prompt(template: str, **kwargs) -> str: def collect_notes(days_back: float) -> list[dict]: """ Collect notes modified within the last `days_back` days. - Skips Briefings and Insights folders to avoid feeding - generated content back into the briefing. Args: days_back: How many days back to look. Use 0 for today only, @@ -129,13 +129,12 @@ def generate_briefing(yesterday_notes: list[dict], today_notes: list[dict], back today_block=today_block, ) - return call_ai(prompt, backend) + return call_ai(prompt, backend, temperature=TEMP_BRIEFING) def write_briefing(content: str) -> str: """ Write the briefing to the Briefings folder in the vault. Creates the folder if it doesn't exist. - Frontmatter is built line by line to avoid f-string issues. Args: content: The briefing Markdown content from generate_briefing(). @@ -146,7 +145,7 @@ def write_briefing(content: str) -> str: BRIEFING_FOLDER.mkdir(parents=True, exist_ok=True) date_str = datetime.datetime.now().strftime("%Y-%m-%d") - filename = f"{date_str} Morning Briefing.md" + filename = BRIEFING_TITLE_FORMAT.format(date=date_str) filepath = BRIEFING_FOLDER / filename fm_lines = [ @@ -186,7 +185,7 @@ def main(): print(f" found {total} note(s) modified in the last 24h\n") if not yesterday_notes and not today_notes: - print(" no recent notes found — briefing will be minimal\n") + print(" no recent notes found\n") print(" generating briefing...") content = generate_briefing(yesterday_notes, today_notes, backend) @@ -196,4 +195,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/study_recap.py b/study_recap.py index d8cefa6..a13b33b 100644 --- a/study_recap.py +++ b/study_recap.py @@ -15,7 +15,7 @@ import datetime import re -from config import EXCLUDED_FOLDERS, MAX_FILE_SIZE, VAULT_PATH, HOURS_BACK, MAX_NOTE_CHARS +from config import EXCLUDED_FOLDERS, MAX_FILE_SIZE, VAULT_PATH, HOURS_BACK, MAX_NOTE_CHARS, TEMPERATURES, RECAP_TITLE_FORMAT from ai_backend import get_backend, call_ai, backend_label, run_startup_checks SCRIPT_DIR = Path(__file__).parent @@ -30,6 +30,8 @@ GROUNDING = PROMPTS["grounding"] +TEMP_RECAP = TEMPERATURES.get("recap") + R = "\033[0m" DIM = "\033[2m" BOLD = "\033[1m" @@ -98,14 +100,13 @@ def find_recent_notes(hours: int) -> list[dict]: def index_all_notes() -> dict[str, str]: """ Build an index of all notes in the vault for connection finding. - Only reads the first 500 chars of each note to keep memory usage low. """ notes = {} for path_obj in VAULT_PATH.rglob("*.md"): try: if path_obj.stat().st_size > MAX_FILE_SIZE: continue - content = path_obj.read_text(encoding="utf-8", errors="ignore")[:500] + content = path_obj.read_text(encoding="utf-8", errors="ignore")[:2000] notes[path_obj.stem] = content except Exception: continue @@ -192,7 +193,7 @@ def generate_recap(notes_data: list[dict], all_notes: dict, backend: str) -> str notes_block=notes_block, all_titles=all_titles, ) - return call_ai(prompt, backend, timeout=600) + return call_ai(prompt, backend, temperature=TEMP_RECAP) def write_recap(content: str, note_names: list[str]) -> str: @@ -210,8 +211,8 @@ def write_recap(content: str, note_names: list[str]) -> str: else: subject = f"{stems[0]}, {stems[1]} (+{len(stems)-2})" - filename = f"{date_str} {time_str} Recap - {subject}.md" - filename = re.sub(r'[\\/*?:"<>|]', "", filename) + safe_subject = re.sub(r'[\\/*?:"<>|]', "", subject) + filename = RECAP_TITLE_FORMAT.format(date=date_str, time=time_str, subject=safe_subject) filepath = RECAP_FOLDER / filename tags_lines = [ diff --git a/txt_to_notes.py b/txt_to_notes.py index fc569f8..8494b89 100644 --- a/txt_to_notes.py +++ b/txt_to_notes.py @@ -3,7 +3,7 @@ # ============================================================= # Converts any text file into one or more structured Obsidian # notes. Works with conversations, meeting notes, articles, -# braindumps — anything you can paste into a .txt file. +# anything you can paste into a .txt file. # # Two-pass process: # Pass 1: AI reads the text and decides how many notes to create, @@ -25,7 +25,7 @@ import datetime from pathlib import Path -from config import MAX_FILE_SIZE, VAULT_PATH +from config import MAX_FILE_SIZE, VAULT_PATH, TEMPERATURES, TXT_TITLE_FORMAT from ai_backend import get_backend, call_ai, backend_label, run_startup_checks SCRIPT_DIR = Path(__file__).parent @@ -48,6 +48,8 @@ GREEN = "\033[32m" RED = "\033[31m" +TEMP_TXT = TEMPERATURES.get("txt") + def format_obsidian_tag(text: str) -> str: text = text.lower().replace(" ", "-") return re.sub(r'[^a-z0-9_-]', '', text) @@ -147,7 +149,7 @@ def plan_notes(text, instructions, existing_tags, existing_titles, backend) -> l titles_hint=titles_hint, ) - raw = call_ai(prompt, backend) + raw = call_ai(prompt, backend, temperature=TEMP_TXT) raw = re.sub(r"```(?:json)?", "", raw).strip() match = re.search(r"\[.*\]", raw, re.DOTALL) @@ -182,7 +184,7 @@ def write_note_content(text, note_plan, all_titles_in_batch, instructions, exist related_links=related_links, tags_hint=tags_hint, ) - return call_ai(prompt, backend, timeout=300) + return call_ai(prompt, backend, temperature=TEMP_TXT) def write_file(note_plan, body, folder_path, date_str) -> str: @@ -190,7 +192,7 @@ def write_file(note_plan, body, folder_path, date_str) -> str: title = note_plan.get("title", "Untitled") tags = [format_obsidian_tag(t) for t in note_plan.get("tags", [])] safe_title = re.sub(r'[\\/*?:"<>|]', "", title) - filename = f"{safe_title}.md" + filename = TXT_TITLE_FORMAT.format(title=safe_title) filepath = Path(folder_path) / filename @@ -280,4 +282,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file