7 Commits

Author SHA1 Message Date
e57aa374ea fix(transcription): Behebt Start- und API-Fehler in der App [2f488f42] 2026-01-26 14:15:23 +00:00
eb3f77f092 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
b396e54080 feat(transcription): add share button to detail view [2f488f42] 2026-01-26 13:07:45 +00:00
c4baf68595 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
0a1be647c4 feat(transcription): add download transcript as txt button
[2f488f42]
2026-01-26 12:36:58 +00:00
01e5ae8b5c 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
adafab61ae 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 requests
import json
import argparse
import shutil
import re
from typing import List, Dict, Optional, Tuple
from getpass import getpass
from dotenv import load_dotenv
load_dotenv()
@@ -87,48 +83,18 @@ def get_page_title(page: Dict) -> str:
return title_parts[0].get("plain_text", "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."""
prop = page.get("properties", {}).get(prop_name)
if not prop:
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", [])
if text_parts:
return text_parts[0].get("plain_text")
return None
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
# Hier könnten weitere Typen wie 'select', 'number' etc. behandelt werden
return None
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}")
return []
def update_notion_task_property(token: str, task_id: str, prop_name: str, prop_value: Any, prop_type: str) -> bool:
"""Aktualisiert eine bestimmte Eigenschaft (Property) eines Notion-Tasks."""
print(f"\n--- Aktualisiere Task '{task_id}', Eigenschaft '{prop_name}' ({prop_type}) mit Wert '{prop_value}'... ---")
def update_notion_task_status(token: str, task_id: str, status_value: str = "Doing") -> bool:
"""Aktualisiert den Status eines Notion-Tasks."""
print(f"\n--- Aktualisiere Status von Task '{task_id}' auf '{status_value}'... ---")
url = f"https://api.notion.com/v1/pages/{task_id}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Notion-Version": "2022-06-28"
}
payload_properties = {}
if prop_type == "status":
payload_properties[prop_name] = {"status": {"name": prop_value}}
elif prop_type == "number":
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}
payload = {
"properties": {
"Status": {
"status": {
"name": status_value
}
}
}
}
try:
response = requests.patch(url, headers=headers, json=payload)
response.raise_for_status()
print(f"✅ Task-Eigenschaft '{prop_name}' erfolgreich aktualisiert.")
print(f"✅ Task-Status erfolgreich auf '{status_value}' aktualisiert.")
return True
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:
print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}")
except:
@@ -439,7 +395,7 @@ def select_project(token: str) -> Optional[Tuple[Dict, Optional[str]]]:
choice = int(input("Bitte wähle eine Nummer: "))
if 1 <= choice <= len(projects):
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
else:
print("Ungültige Auswahl.")
@@ -535,68 +491,13 @@ def report_status_to_notion(
session_data = json.load(f)
task_id = session_data.get("task_id")
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):
print("❌ FEHLER: Session-Daten unvollständig oder Startzeit fehlt. 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}")
if not (task_id and token):
print("❌ FEHLER: Session-Daten unvollständig. Kann keinen Statusbericht erstellen.")
return
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)
actual_git_changes = git_changes_override
actual_commit_messages = commit_messages_override
@@ -700,7 +601,7 @@ def report_status_to_notion(
# Notion aktualisieren
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:
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.
def save_session_info(task_id: str, token: str, session_start_time: datetime):
"""Speichert die Task-ID, den Token und die Startzeit der Session."""
import shutil
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)
session_data = {
"task_id": task_id,
"token": token,
"session_start_time": session_start_time.isoformat() # Speichern als ISO-Format String
"token": token
}
with open(SESSION_FILE_PATH, "w") as f:
json.dump(session_data, f)
@@ -805,7 +713,7 @@ def complete_session():
status_options = get_database_status_options(token, tasks_db_id)
if status_options:
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):
print("Fehler beim Lesen der Session-Informationen.")
@@ -863,7 +771,7 @@ def start_interactive_session():
task_description = get_page_content(token, task_id)
# 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
install_git_hook()
@@ -874,7 +782,7 @@ def start_interactive_session():
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:
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.