7 Commits

Author SHA1 Message Date
4d26cca645 fix(transcription): Behebt Start- und API-Fehler in der App [2f488f42] 2026-01-26 14:15:23 +00:00
c410c9b3c0 feat(notion): Append status reports directly to page content
- Replaces the Notion update mechanism to append content blocks to the task page instead of posting comments.
- A new function, , is implemented to handle the Notion Block API.
- The  function now formats the report into a 'heading_2' block for the title and a 'code' block for the detailed content, preserving formatting.
- This provides a much cleaner and more readable changelog directly within the Notion task description.
2026-01-26 13:16:52 +00:00
a1a0d46b63 feat(transcription): add share button to detail view [2f488f42] 2026-01-26 13:07:45 +00:00
6e38c85de8 refactor(workflow): Enhance Notion reporting and context awareness
- Adds a '--summary' parameter to dev_session.py to allow for detailed, narrative descriptions in Notion status updates.
- The Notion comment format is updated to prominently display this summary.

- start-gemini.sh is refactored to be more robust and context-aware.
- It now injects the container name and a strict rule against nested docker commands into the Gemini CLI's initial prompt.
- This prevents operational errors and provides better context for the agent.
2026-01-26 12:51:53 +00:00
5d6bcbd0dd feat(transcription): add download transcript as txt button
[2f488f42]
2026-01-26 12:36:58 +00:00
fbf59bfb71 feat(dev_session): Add agent-driven Notion status reporting
Implements the  functionality in , allowing the Gemini agent to non-interactively update a Notion task with a detailed progress summary.

The agent can now be prompted to:
- Collect the new task status and any open to-dos.
- Generate a summary of Git changes () and commit messages.
- Post a formatted report as a comment to the Notion task.
- Update the task's status property.

The  has been updated to document this new agent-centric workflow, detailing how to start a session, work within it, and use the agent to report progress and push changes seamlessly.
2026-01-26 12:24:26 +00:00
9019a801ed fix(transcription): [2f388f42] finalize and fix AI insights feature
This commit resolves all outstanding issues with the AI Insights feature.

- Corrects the transcript formatting logic in  to properly handle the database JSON structure, ensuring the AI receives the correct context.
- Fixes the Gemini API client by using the correct model name ('gemini-2.0-flash') and the proper client initialization.
- Updates  to securely pass the API key as an environment variable to the container.
- Cleans up the codebase by removing temporary debugging endpoints.
- Adds  script for programmatic updates.
- Updates documentation with troubleshooting insights from the implementation process.
2026-01-26 08:53:13 +00:00
12 changed files with 40 additions and 132 deletions

View File

@@ -1 +1 @@
{"task_id": "2f488f42-8544-81ac-a9f8-e373c4c18115", "token": "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", "session_start_time": "2026-01-26T18:39:11.157549"} {"task_id": "2f488f42-8544-819a-8407-f29748b3e0b8", "token": "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"}

View File

@@ -1,14 +1,10 @@
import re
from typing import List, Dict, Optional, Tuple, Any
from getpass import getpass
from dotenv import load_dotenv
import subprocess
from datetime import datetime, timedelta
import os import os
import requests import requests
import json import json
import argparse import re
import shutil from typing import List, Dict, Optional, Tuple
from getpass import getpass
from dotenv import load_dotenv
load_dotenv() load_dotenv()
@@ -87,48 +83,18 @@ def get_page_title(page: Dict) -> str:
return title_parts[0].get("plain_text", "Unbenannt") return title_parts[0].get("plain_text", "Unbenannt")
return "Unbenannt" return "Unbenannt"
def get_page_rich_text_property(page: Dict, prop_name: str) -> Optional[str]: def get_page_property(page: Dict, prop_name: str, prop_type: str = "rich_text") -> Optional[str]:
"""Extrahiert den Inhalt einer bestimmten Eigenschaft (Property) von einer Seite.""" """Extrahiert den Inhalt einer bestimmten Eigenschaft (Property) von einer Seite."""
prop = page.get("properties", {}).get(prop_name) prop = page.get("properties", {}).get(prop_name)
if not prop: if not prop:
return None return None
if prop.get("type") == "rich_text": if prop_type == "rich_text" and prop.get("type") == "rich_text":
text_parts = prop.get("rich_text", []) text_parts = prop.get("rich_text", [])
if text_parts: if text_parts:
return text_parts[0].get("plain_text") return text_parts[0].get("plain_text")
return None # Hier könnten weitere Typen wie 'select', 'number' etc. behandelt werden
def get_page_number_property(page: Dict, prop_name: str) -> Optional[float]:
"""Extrahiert den Inhalt einer Number-Eigenschaft von einer Seite."""
prop = page.get("properties", {}).get(prop_name)
if not prop:
return None
if prop.get("type") == "number":
return prop.get("number")
return None
def get_page_date_property(page: Dict, prop_name: str) -> Optional[datetime]:
"""Extrahiert den Inhalt einer Date-Eigenschaft von einer Seite."""
prop = page.get("properties", {}).get(prop_name)
if not prop:
return None
if prop.get("type") == "date":
date_info = prop.get("date")
if date_info and date_info.get("start"):
try:
# Notion gibt Datumszeiten im ISO 8601 Format zurück (z.B. '2023-10-27T10:00:00.000+00:00')
# oder nur Datum (z.B. '2023-10-27')
# datetime.fromisoformat kann beides verarbeiten, aber Zeitzonen können komplex sein.
# Für unsere Zwecke reicht es, wenn es als UTC betrachtet wird und wir die Dauer berechnen.
return datetime.fromisoformat(date_info["start"].replace('Z', '+00:00'))
except ValueError:
return None
return None return None
def get_page_content(token: str, page_id: str) -> str: def get_page_content(token: str, page_id: str) -> str:
@@ -227,42 +193,32 @@ def get_database_status_options(token: str, db_id: str) -> List[str]:
print(f"Fehler beim Abrufen der Datenbank-Eigenschaften: {e}") print(f"Fehler beim Abrufen der Datenbank-Eigenschaften: {e}")
return [] return []
def update_notion_task_property(token: str, task_id: str, prop_name: str, prop_value: Any, prop_type: str) -> bool: def update_notion_task_status(token: str, task_id: str, status_value: str = "Doing") -> bool:
"""Aktualisiert eine bestimmte Eigenschaft (Property) eines Notion-Tasks.""" """Aktualisiert den Status eines Notion-Tasks."""
print(f"\n--- Aktualisiere Task '{task_id}', Eigenschaft '{prop_name}' ({prop_type}) mit Wert '{prop_value}'... ---") print(f"\n--- Aktualisiere Status von Task '{task_id}' auf '{status_value}'... ---")
url = f"https://api.notion.com/v1/pages/{task_id}" url = f"https://api.notion.com/v1/pages/{task_id}"
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
"Content-Type": "application/json", "Content-Type": "application/json",
"Notion-Version": "2022-06-28" "Notion-Version": "2022-06-28"
} }
payload = {
payload_properties = {} "properties": {
if prop_type == "status": "Status": {
payload_properties[prop_name] = {"status": {"name": prop_value}} "status": {
elif prop_type == "number": "name": status_value
payload_properties[prop_name] = {"number": prop_value} }
elif prop_type == "date": }
# Notion erwartet Datum im ISO 8601 Format }
if isinstance(prop_value, datetime): }
payload_properties[prop_name] = {"date": {"start": prop_value.isoformat()}}
else:
print(f"❌ FEHLER: Ungültiges Datumformat für '{prop_name}'. Erwartet datetime-Objekt.")
return False
# Weitere Typen können hier hinzugefügt werden (z.B. rich_text, multi_select etc.)
else:
print(f"❌ FEHLER: Nicht unterstützter Eigenschaftstyp '{prop_type}' für Update.")
return False
payload = {"properties": payload_properties}
try: try:
response = requests.patch(url, headers=headers, json=payload) response = requests.patch(url, headers=headers, json=payload)
response.raise_for_status() response.raise_for_status()
print(f"✅ Task-Eigenschaft '{prop_name}' erfolgreich aktualisiert.") print(f"✅ Task-Status erfolgreich auf '{status_value}' aktualisiert.")
return True return True
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print(f"❌ FEHLER beim Aktualisieren der Task-Eigenschaft '{prop_name}': {e}") print(f"❌ FEHLER beim Aktualisieren des Task-Status: {e}")
try: try:
print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}") print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}")
except: except:
@@ -439,7 +395,7 @@ def select_project(token: str) -> Optional[Tuple[Dict, Optional[str]]]:
choice = int(input("Bitte wähle eine Nummer: ")) choice = int(input("Bitte wähle eine Nummer: "))
if 1 <= choice <= len(projects): if 1 <= choice <= len(projects):
selected_project = projects[choice - 1] selected_project = projects[choice - 1]
readme_path = get_page_rich_text_property(selected_project, "Readme Path") readme_path = get_page_property(selected_project, "Readme Path")
return selected_project, readme_path return selected_project, readme_path
else: else:
print("Ungültige Auswahl.") print("Ungültige Auswahl.")
@@ -535,68 +491,13 @@ def report_status_to_notion(
session_data = json.load(f) session_data = json.load(f)
task_id = session_data.get("task_id") task_id = session_data.get("task_id")
token = session_data.get("token") token = session_data.get("token")
session_start_time_str = session_data.get("session_start_time")
if not (task_id and token and session_start_time_str): if not (task_id and token):
print("❌ FEHLER: Session-Daten unvollständig oder Startzeit fehlt. Kann keinen Statusbericht erstellen.") print("❌ FEHLER: Session-Daten unvollständig. Kann keinen Statusbericht erstellen.")
return
try:
session_start_time = datetime.fromisoformat(session_start_time_str)
except ValueError:
print(f"❌ FEHLER: Ungültiges Startzeitformat in Session-Daten: {session_start_time_str}")
return return
print(f"--- Erstelle Statusbericht für Task {task_id} ---") print(f"--- Erstelle Statusbericht für Task {task_id} ---")
tasks_db_id = find_database_by_title(token, "Tasks [UT]")
if not tasks_db_id:
return
# 1. Aktuelles Task-Objekt abrufen, um die vorhandene Dauer zu lesen
page_url = f"https://api.notion.com/v1/pages/{task_id}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Notion-Version": "2022-06-28"
}
current_task_page = None
try:
response = requests.get(page_url, headers=headers)
response.raise_for_status()
current_task_page = response.json()
except requests.exceptions.RequestException as e:
print(f"❌ FEHLER beim Abrufen der Task-Seite {task_id}: {e}")
return
if not current_task_page:
print(f"❌ FEHLER: Konnte den Task {task_id} in Notion nicht finden. Zeiterfassung wird übersprungen.")
return
# 2. Bestehende Total Duration (h) abrufen
existing_duration = get_page_number_property(current_task_page, "Total Duration (h)")
if existing_duration is None:
existing_duration = 0.0
# 3. Dauer der aktuellen Arbeitseinheit berechnen
current_end_time = datetime.now()
time_spent = (current_end_time - session_start_time).total_seconds() / 3600.0 # Dauer in Stunden
# 4. Neue Gesamtdauer berechnen
new_total_duration = existing_duration + time_spent
# 5. Total Duration (h) in Notion aktualisieren
update_notion_task_property(token, task_id, "Total Duration (h)", round(new_total_duration, 2), "number")
# 6. session_start_time in SESSION_INFO für die nächste Arbeitseinheit aktualisieren
# Hier speichern wir die Endzeit als neue Startzeit für die nächste mögliche Einheit
# oder löschen sie, wenn die Session als beendet betrachtet wird.
# Entsprechend des Plans setzen wir sie zurück (auf die aktuelle Endzeit der Arbeitseinheit).
session_data["session_start_time"] = current_end_time.isoformat()
with open(SESSION_FILE_PATH, "w") as f:
json.dump(session_data, f)
# Git-Zusammenfassung generieren (immer, wenn nicht explizit überschrieben) # Git-Zusammenfassung generieren (immer, wenn nicht explizit überschrieben)
actual_git_changes = git_changes_override actual_git_changes = git_changes_override
actual_commit_messages = commit_messages_override actual_commit_messages = commit_messages_override
@@ -700,7 +601,7 @@ def report_status_to_notion(
# Notion aktualisieren # Notion aktualisieren
append_blocks_to_notion_page(token, task_id, notion_blocks) append_blocks_to_notion_page(token, task_id, notion_blocks)
update_notion_task_property(token, task_id, "Status", actual_status, "status") update_notion_task_status(token, task_id, actual_status)
except (FileNotFoundError, json.JSONDecodeError) as e: except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"❌ FEHLER beim Lesen der Session-Informationen für Statusbericht: {e}") print(f"❌ FEHLER beim Lesen der Session-Informationen für Statusbericht: {e}")
@@ -740,13 +641,20 @@ def generate_cli_context(project_title: str, task_title: str, task_id: str, read
# Die start_gemini_cli Funktion wird entfernt, da das aufrufende Skript jetzt die Gemini CLI startet. # Die start_gemini_cli Funktion wird entfernt, da das aufrufende Skript jetzt die Gemini CLI startet.
def save_session_info(task_id: str, token: str, session_start_time: datetime): import shutil
"""Speichert die Task-ID, den Token und die Startzeit der Session.""" import argparse
# --- Session Management ---
SESSION_DIR = ".dev_session"
SESSION_FILE_PATH = os.path.join(SESSION_DIR, "SESSION_INFO")
def save_session_info(task_id: str, token: str):
"""Speichert die Task-ID und den Token für den Git-Hook."""
os.makedirs(SESSION_DIR, exist_ok=True) os.makedirs(SESSION_DIR, exist_ok=True)
session_data = { session_data = {
"task_id": task_id, "task_id": task_id,
"token": token, "token": token
"session_start_time": session_start_time.isoformat() # Speichern als ISO-Format String
} }
with open(SESSION_FILE_PATH, "w") as f: with open(SESSION_FILE_PATH, "w") as f:
json.dump(session_data, f) json.dump(session_data, f)
@@ -805,7 +713,7 @@ def complete_session():
status_options = get_database_status_options(token, tasks_db_id) status_options = get_database_status_options(token, tasks_db_id)
if status_options: if status_options:
done_status = status_options[-1] done_status = status_options[-1]
update_notion_task_property(token, task_id, "Status", done_status, "status") update_notion_task_status(token, task_id, done_status)
except (FileNotFoundError, json.JSONDecodeError): except (FileNotFoundError, json.JSONDecodeError):
print("Fehler beim Lesen der Session-Informationen.") print("Fehler beim Lesen der Session-Informationen.")
@@ -863,7 +771,7 @@ def start_interactive_session():
task_description = get_page_content(token, task_id) task_description = get_page_content(token, task_id)
# Session-Informationen für den Git-Hook speichern # Session-Informationen für den Git-Hook speichern
save_session_info(task_id, token, datetime.now()) save_session_info(task_id, token)
# Git-Hook installieren, der die Session-Infos nutzt # Git-Hook installieren, der die Session-Infos nutzt
install_git_hook() install_git_hook()
@@ -874,7 +782,7 @@ def start_interactive_session():
suggested_branch_name = f"feature/task-{task_id.split('-')[0]}-{title_slug}" suggested_branch_name = f"feature/task-{task_id.split('-')[0]}-{title_slug}"
status_updated = update_notion_task_property(token, task_id, "Status", "Doing", "status") status_updated = update_notion_task_status(token, task_id, "Doing")
if not status_updated: if not status_updated:
print("Warnung: Notion-Task-Status konnte nicht aktualisiert werden.") print("Warnung: Notion-Task-Status konnte nicht aktualisiert werden.")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.