|
""" |
|
会話履歴自動記録システム |
|
======================= |
|
|
|
GitHub Copilotとの会話を自動的にSQLiteに保存するためのフックシステム |
|
|
|
使用方法: |
|
1. このモジュールをインポート |
|
2. log_conversation()を呼び出すだけで自動保存 |
|
3. セッション管理も自動化 |
|
""" |
|
|
|
import uuid |
|
import datetime |
|
import json |
|
import os |
|
import traceback |
|
import sqlite3 |
|
import subprocess |
|
from typing import Optional, Dict, List |
|
from controllers.conversation_history import ConversationManager |
|
|
|
class ConversationLogger: |
|
def __init__(self): |
|
"""会話ログシステムの初期化""" |
|
self.conversation_manager = ConversationManager() |
|
self.current_session_id = self.generate_session_id() |
|
self.session_start_time = datetime.datetime.now() |
|
|
|
print(f"🎯 会話ログシステム開始 - セッションID: {self.current_session_id[:8]}") |
|
|
|
def generate_session_id(self) -> str: |
|
"""新しいセッションIDを生成""" |
|
return str(uuid.uuid4()) |
|
|
|
def start_new_session(self, session_name: str = None) -> str: |
|
"""新しいセッションを開始""" |
|
self.current_session_id = self.generate_session_id() |
|
self.session_start_time = datetime.datetime.now() |
|
|
|
if session_name: |
|
|
|
try: |
|
conn = sqlite3.connect(self.conversation_manager.db_path) |
|
cursor = conn.cursor() |
|
cursor.execute(''' |
|
UPDATE sessions |
|
SET session_name = ? |
|
WHERE session_id = ? |
|
''', (session_name, self.current_session_id)) |
|
conn.commit() |
|
conn.close() |
|
except Exception as e: |
|
print(f"⚠️ セッション名更新エラー: {e}") |
|
|
|
print(f"🆕 新しいセッション開始: {self.current_session_id[:8]}") |
|
return self.current_session_id |
|
|
|
def log_conversation(self, |
|
user_message: str, |
|
assistant_response: str, |
|
context_info: str = "", |
|
files_involved: List[str] = None, |
|
tools_used: List[str] = None, |
|
tags: List[str] = None, |
|
project_name: str = "ContBK統合システム") -> Optional[int]: |
|
""" |
|
会話を自動記録 |
|
|
|
Args: |
|
user_message: ユーザーからのメッセージ |
|
assistant_response: アシスタントの応答 |
|
context_info: コンテキスト情報 |
|
files_involved: 関連ファイルのリスト |
|
tools_used: 使用ツールのリスト |
|
tags: タグのリスト |
|
project_name: プロジェクト名 |
|
|
|
Returns: |
|
会話ID (保存に成功した場合) |
|
""" |
|
try: |
|
|
|
files_str = ", ".join(files_involved) if files_involved else "" |
|
tools_str = ", ".join(tools_used) if tools_used else "" |
|
tags_str = ", ".join(tags) if tags else "" |
|
|
|
|
|
conversation_id = self.conversation_manager.save_conversation( |
|
session_id=self.current_session_id, |
|
user_message=user_message, |
|
assistant_response=assistant_response, |
|
context_info=context_info, |
|
files_involved=files_str, |
|
tools_used=tools_str, |
|
tags=tags_str |
|
) |
|
|
|
print(f"✅ 会話を記録しました (ID: {conversation_id})") |
|
return conversation_id |
|
|
|
except Exception as e: |
|
print(f"❌ 会話記録エラー: {e}") |
|
print(traceback.format_exc()) |
|
return None |
|
|
|
def log_tool_usage(self, tool_name: str, parameters: Dict, result: str): |
|
"""ツール使用ログを記録""" |
|
tool_info = { |
|
"tool": tool_name, |
|
"parameters": parameters, |
|
"result": result[:500], |
|
"timestamp": datetime.datetime.now().isoformat() |
|
} |
|
|
|
|
|
try: |
|
conn = sqlite3.connect(self.conversation_manager.db_path) |
|
cursor = conn.cursor() |
|
|
|
|
|
cursor.execute(''' |
|
SELECT id, tools_used FROM conversations |
|
WHERE session_id = ? |
|
ORDER BY timestamp DESC |
|
LIMIT 1 |
|
''', (self.current_session_id,)) |
|
|
|
row = cursor.fetchone() |
|
if row: |
|
conversation_id, existing_tools = row |
|
|
|
|
|
updated_tools = existing_tools + f", {tool_name}" if existing_tools else tool_name |
|
|
|
cursor.execute(''' |
|
UPDATE conversations |
|
SET tools_used = ?, updated_at = CURRENT_TIMESTAMP |
|
WHERE id = ? |
|
''', (updated_tools, conversation_id)) |
|
|
|
conn.commit() |
|
|
|
conn.close() |
|
print(f"🔧 ツール使用を記録: {tool_name}") |
|
|
|
except Exception as e: |
|
print(f"⚠️ ツール使用記録エラー: {e}") |
|
|
|
def get_session_summary(self) -> Dict: |
|
"""現在のセッションの要約を取得""" |
|
try: |
|
conversations = self.conversation_manager.get_conversations( |
|
session_id=self.current_session_id |
|
) |
|
|
|
return { |
|
"session_id": self.current_session_id, |
|
"start_time": self.session_start_time.isoformat(), |
|
"conversation_count": len(conversations), |
|
"duration_minutes": (datetime.datetime.now() - self.session_start_time).total_seconds() / 60, |
|
"latest_conversation": conversations[0] if conversations else None |
|
} |
|
except Exception as e: |
|
print(f"⚠️ セッション要約取得エラー: {e}") |
|
return {} |
|
|
|
def export_session(self, session_id: str = None) -> str: |
|
"""セッションをJSON形式でエクスポート""" |
|
target_session = session_id or self.current_session_id |
|
|
|
try: |
|
conversations = self.conversation_manager.get_conversations( |
|
session_id=target_session |
|
) |
|
|
|
export_data = { |
|
"session_id": target_session, |
|
"export_time": datetime.datetime.now().isoformat(), |
|
"conversation_count": len(conversations), |
|
"conversations": conversations |
|
} |
|
|
|
filename = f"session_export_{target_session[:8]}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json" |
|
|
|
with open(filename, 'w', encoding='utf-8') as f: |
|
json.dump(export_data, f, ensure_ascii=False, indent=2) |
|
|
|
print(f"📥 セッションエクスポート完了: {filename}") |
|
return filename |
|
|
|
except Exception as e: |
|
print(f"❌ セッションエクスポートエラー: {e}") |
|
return "" |
|
|
|
def create_github_issue(self, |
|
title: str = None, |
|
session_id: str = None, |
|
labels: List[str] = None, |
|
assignee: str = None) -> bool: |
|
""" |
|
GitHub Issueを作成 |
|
|
|
Args: |
|
title: Issue のタイトル(未指定の場合は自動生成) |
|
session_id: 対象セッションID(未指定の場合は現在のセッション) |
|
labels: 付与するラベル |
|
assignee: アサイニー |
|
|
|
Returns: |
|
作成成功の可否 |
|
""" |
|
try: |
|
target_session = session_id or self.current_session_id |
|
|
|
|
|
conversations = self.conversation_manager.get_conversations( |
|
session_id=target_session |
|
) |
|
|
|
if not conversations: |
|
print("⚠️ 会話履歴が見つかりません") |
|
return False |
|
|
|
|
|
if not title: |
|
first_conversation = conversations[-1] |
|
title = f"開発セッション: {first_conversation.get('user_message', '')[:50]}..." |
|
|
|
|
|
issue_body = self._generate_issue_body(conversations, target_session) |
|
|
|
|
|
cmd = [ |
|
'gh', 'issue', 'create', |
|
'--title', title, |
|
'--body', issue_body |
|
] |
|
|
|
|
|
if labels: |
|
for label in labels: |
|
cmd.extend(['--label', label]) |
|
|
|
|
|
if assignee: |
|
cmd.extend(['--assignee', assignee]) |
|
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, cwd='/workspaces/fastapi_django_main_live') |
|
|
|
if result.returncode == 0: |
|
issue_url = result.stdout.strip() |
|
print(f"✅ GitHub Issue作成成功: {issue_url}") |
|
|
|
|
|
self._update_session_issue_url(target_session, issue_url) |
|
|
|
return True |
|
else: |
|
print(f"❌ GitHub Issue作成失敗: {result.stderr}") |
|
return False |
|
|
|
except Exception as e: |
|
print(f"❌ GitHub Issue作成エラー: {e}") |
|
print(traceback.format_exc()) |
|
return False |
|
|
|
def _generate_issue_body(self, conversations: List[Dict], session_id: str) -> str: |
|
"""Issue本文を生成""" |
|
|
|
|
|
session_info = self.get_session_summary() |
|
|
|
body_parts = [ |
|
"# 開発セッション記録", |
|
"", |
|
"## 📊 セッション情報", |
|
f"- **セッションID**: `{session_id[:8]}`", |
|
f"- **開始時刻**: {self.session_start_time.strftime('%Y-%m-%d %H:%M:%S')}", |
|
f"- **会話数**: {len(conversations)}", |
|
f"- **継続時間**: {session_info.get('duration_minutes', 0):.1f}分", |
|
"", |
|
"## 🗣️ 会話履歴", |
|
"" |
|
] |
|
|
|
|
|
recent_conversations = conversations[:10] if len(conversations) > 10 else conversations |
|
|
|
for i, conv in enumerate(reversed(recent_conversations), 1): |
|
body_parts.extend([ |
|
f"### {i}. {conv.get('timestamp', '')}", |
|
"", |
|
"**👤 User:**", |
|
"```", |
|
conv.get('user_message', ''), |
|
"```", |
|
"", |
|
"**🤖 Assistant:**", |
|
"```", |
|
conv.get('assistant_response', '')[:1000] + ('...' if len(conv.get('assistant_response', '')) > 1000 else ''), |
|
"```", |
|
"" |
|
]) |
|
|
|
|
|
if conv.get('context_info'): |
|
body_parts.extend([ |
|
"**📝 Context:**", |
|
"```", |
|
conv.get('context_info', ''), |
|
"```", |
|
"" |
|
]) |
|
|
|
|
|
if conv.get('files_involved'): |
|
body_parts.extend([ |
|
"**📁 Files:**", |
|
f"`{conv.get('files_involved', '')}`", |
|
"" |
|
]) |
|
|
|
|
|
if conv.get('tools_used'): |
|
body_parts.extend([ |
|
"**🔧 Tools:**", |
|
f"`{conv.get('tools_used', '')}`", |
|
"" |
|
]) |
|
|
|
body_parts.append("---") |
|
body_parts.append("") |
|
|
|
|
|
all_files = set() |
|
all_tools = set() |
|
all_tags = set() |
|
|
|
for conv in conversations: |
|
if conv.get('files_involved'): |
|
all_files.update(conv.get('files_involved', '').split(', ')) |
|
if conv.get('tools_used'): |
|
all_tools.update(conv.get('tools_used', '').split(', ')) |
|
if conv.get('tags'): |
|
all_tags.update(conv.get('tags', '').split(', ')) |
|
|
|
body_parts.extend([ |
|
"## 📋 セッション要約", |
|
"", |
|
"### 関連ファイル", |
|
"", |
|
]) |
|
|
|
for file in sorted(all_files): |
|
if file.strip(): |
|
body_parts.append(f"- `{file.strip()}`") |
|
|
|
body_parts.extend([ |
|
"", |
|
"### 使用ツール", |
|
"", |
|
]) |
|
|
|
for tool in sorted(all_tools): |
|
if tool.strip(): |
|
body_parts.append(f"- `{tool.strip()}`") |
|
|
|
if all_tags: |
|
body_parts.extend([ |
|
"", |
|
"### タグ", |
|
"", |
|
]) |
|
|
|
for tag in sorted(all_tags): |
|
if tag.strip(): |
|
body_parts.append(f"- `{tag.strip()}`") |
|
|
|
body_parts.extend([ |
|
"", |
|
"---", |
|
f"*自動生成: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*" |
|
]) |
|
|
|
return "\n".join(body_parts) |
|
|
|
def _update_session_issue_url(self, session_id: str, issue_url: str): |
|
"""セッションにIssue URLを記録""" |
|
try: |
|
conn = sqlite3.connect(self.conversation_manager.db_path) |
|
cursor = conn.cursor() |
|
|
|
|
|
cursor.execute(''' |
|
ALTER TABLE sessions ADD COLUMN issue_url TEXT |
|
''') |
|
|
|
except sqlite3.OperationalError: |
|
|
|
pass |
|
|
|
try: |
|
cursor.execute(''' |
|
INSERT OR REPLACE INTO sessions (session_id, issue_url) |
|
VALUES (?, ?) |
|
''', (session_id, issue_url)) |
|
|
|
conn.commit() |
|
conn.close() |
|
|
|
except Exception as e: |
|
print(f"⚠️ Issue URL記録エラー: {e}") |
|
|
|
def create_issue_for_current_session(self, |
|
title: str = None, |
|
labels: List[str] = None) -> bool: |
|
"""現在のセッションのGitHub Issueを作成""" |
|
default_labels = [] |
|
if labels: |
|
default_labels.extend(labels) |
|
|
|
return self.create_github_issue( |
|
title=title, |
|
session_id=self.current_session_id, |
|
labels=default_labels |
|
) |
|
|
|
def create_quick_issue(title: str, |
|
user_msg: str, |
|
assistant_msg: str, |
|
labels: List[str] = None): |
|
""" |
|
会話内容から直接GitHub Issueを作成 |
|
|
|
使用例: |
|
create_quick_issue( |
|
title="ContBK統合システム開発", |
|
user_msg="このやりとりをGit Issueへ登録したい", |
|
assistant_msg="GitHub Issue作成機能を実装しました", |
|
labels=["development", "enhancement"] |
|
) |
|
""" |
|
|
|
conversation_logger.log_conversation( |
|
user_message=user_msg, |
|
assistant_response=assistant_msg, |
|
context_info="GitHub Issue直接作成", |
|
tags=["quick-issue"] + (labels or []) |
|
) |
|
|
|
|
|
return conversation_logger.create_issue_for_current_session( |
|
title=title, |
|
labels=labels or ["development", "conversation-log"] |
|
) |
|
|
|
|
|
conversation_logger = ConversationLogger() |
|
|
|
def log_this_conversation(user_msg: str, assistant_msg: str, |
|
context: str = "", files: List[str] = None, |
|
tools: List[str] = None, tags: List[str] = None): |
|
""" |
|
簡単な会話ログ記録関数 |
|
|
|
使用例: |
|
log_this_conversation( |
|
user_msg="ContBK統合システムについて教えて", |
|
assistant_msg="ContBK統合システムは...", |
|
files=["controllers/contbk_example.py"], |
|
tools=["create_file", "insert_edit_into_file"], |
|
tags=["contbk", "gradio"] |
|
) |
|
""" |
|
return conversation_logger.log_conversation( |
|
user_message=user_msg, |
|
assistant_response=assistant_msg, |
|
context_info=context, |
|
files_involved=files, |
|
tools_used=tools, |
|
tags=tags |
|
) |
|
|
|
def start_new_conversation_session(session_name: str = None): |
|
"""新しい会話セッションを開始""" |
|
return conversation_logger.start_new_session(session_name) |
|
|
|
def create_quick_issue(title: str, |
|
user_msg: str, |
|
assistant_msg: str, |
|
labels: List[str] = None): |
|
""" |
|
会話内容から直接GitHub Issueを作成 |
|
|
|
使用例: |
|
create_quick_issue( |
|
title="ContBK統合システム開発", |
|
user_msg="このやりとりをGit Issueへ登録したい", |
|
assistant_msg="GitHub Issue作成機能を実装しました", |
|
labels=["development", "enhancement"] |
|
) |
|
""" |
|
|
|
conversation_logger.log_conversation( |
|
user_message=user_msg, |
|
assistant_response=assistant_msg, |
|
context_info="GitHub Issue直接作成", |
|
tags=["quick-issue"] + (labels or []) |
|
) |
|
|
|
|
|
return conversation_logger.create_issue_for_current_session( |
|
title=title, |
|
labels=labels or [] |
|
) |
|
|
|
def create_github_issue_for_session(title: str = None, |
|
labels: List[str] = None, |
|
session_id: str = None): |
|
""" |
|
現在のセッションまたは指定セッションのGitHub Issueを作成 |
|
|
|
使用例: |
|
create_github_issue_for_session( |
|
title="ContBK統合システム開発セッション", |
|
labels=["enhancement", "contbk"] |
|
) |
|
""" |
|
return conversation_logger.create_github_issue( |
|
title=title, |
|
session_id=session_id, |
|
labels=labels or ["development", "conversation-log"] |
|
) |
|
|
|
def create_issue_now(title: str = "開発セッション記録"): |
|
"""ワンクリックでGitHub Issue作成""" |
|
return conversation_logger.create_issue_for_current_session(title=title) |
|
|
|
def get_current_session_info(): |
|
"""現在のセッション情報を取得""" |
|
return conversation_logger.get_session_summary() |
|
|
|
|
|
def auto_log_conversation(tags: List[str] = None): |
|
""" |
|
関数の実行を自動的にログに記録するデコレーター |
|
|
|
使用例: |
|
@auto_log_conversation(tags=["gradio", "interface"]) |
|
def create_interface(): |
|
# 関数の処理 |
|
pass |
|
""" |
|
def decorator(func): |
|
def wrapper(*args, **kwargs): |
|
start_time = datetime.datetime.now() |
|
|
|
try: |
|
result = func(*args, **kwargs) |
|
|
|
|
|
conversation_logger.log_conversation( |
|
user_message=f"関数実行: {func.__name__}", |
|
assistant_response=f"関数 {func.__name__} が正常に実行されました", |
|
context_info=f"実行時間: {(datetime.datetime.now() - start_time).total_seconds():.2f}秒", |
|
tools_used=[func.__name__], |
|
tags=tags or ["自動実行"] |
|
) |
|
|
|
return result |
|
|
|
except Exception as e: |
|
|
|
conversation_logger.log_conversation( |
|
user_message=f"関数実行エラー: {func.__name__}", |
|
assistant_response=f"エラーが発生しました: {str(e)}", |
|
context_info=f"実行時間: {(datetime.datetime.now() - start_time).total_seconds():.2f}秒", |
|
tools_used=[func.__name__], |
|
tags=(tags or []) + ["エラー"] |
|
) |
|
raise |
|
|
|
return wrapper |
|
return decorator |
|
|
|
if __name__ == "__main__": |
|
|
|
print("🧪 会話ログシステムテスト") |
|
|
|
|
|
log_this_conversation( |
|
user_msg="GitHub Issue作成機能のテストです", |
|
assistant_msg="GitHub Issue作成機能が正常に動作しています!", |
|
context="GitHub Issue機能追加", |
|
files=["controllers/conversation_logger.py"], |
|
tools=["create_github_issue", "gh"], |
|
tags=["github", "issue", "automation"] |
|
) |
|
|
|
|
|
session_info = get_current_session_info() |
|
print(f"📊 セッション情報: {session_info}") |
|
|
|
|
|
|
|
|
|
print("✅ テスト完了") |
|
|