mirror of
https://github.com/tradecatlabs/vibe-coding-cn.git
synced 2026-08-17 21:08:05 +00:00
feat: Add new files for i18n, libs, and project structure
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
from .codex import CodexParser
|
||||
from .gemini import GeminiParser
|
||||
from .claude import ClaudeParser
|
||||
from .kiro import KiroParser
|
||||
from .base import SessionData
|
||||
|
||||
__all__ = ["CodexParser", "GeminiParser", "ClaudeParser", "KiroParser", "SessionData"]
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Dict
|
||||
|
||||
@dataclass
|
||||
class SessionData:
|
||||
"""会话数据"""
|
||||
session_id: str
|
||||
source: str
|
||||
file_path: str
|
||||
file_mtime: float = 0
|
||||
cwd: str = None
|
||||
messages: List[Dict] = field(default_factory=list) # [{"time", "role", "content"}]
|
||||
|
||||
class BaseParser(ABC):
|
||||
@abstractmethod
|
||||
def find_files(self, paths: list) -> list:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def parse_file(self, filepath: str) -> SessionData:
|
||||
pass
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
from .base import BaseParser, SessionData
|
||||
|
||||
class ClaudeParser(BaseParser):
|
||||
def find_files(self, paths: list) -> list:
|
||||
files = []
|
||||
for base in paths:
|
||||
history = os.path.join(base, "history.jsonl")
|
||||
if os.path.exists(history):
|
||||
files.append(history)
|
||||
return files
|
||||
|
||||
def parse_file(self, filepath: str) -> list:
|
||||
"""返回多个 SessionData(按 project 分组)"""
|
||||
projects = defaultdict(list)
|
||||
file_mtime = os.path.getmtime(filepath)
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
data = json.loads(line)
|
||||
content = data.get('display', '')
|
||||
if not content:
|
||||
continue
|
||||
|
||||
project = data.get('project', 'unknown')
|
||||
ts_ms = data.get('timestamp', 0)
|
||||
ts = datetime.fromtimestamp(ts_ms / 1000).isoformat() if ts_ms else ''
|
||||
|
||||
projects[project].append({
|
||||
'time': ts,
|
||||
'role': 'user',
|
||||
'content': content
|
||||
})
|
||||
|
||||
return [
|
||||
SessionData(
|
||||
session_id='claude-' + hashlib.md5(proj.encode()).hexdigest()[:12],
|
||||
source='claude',
|
||||
file_path=filepath,
|
||||
file_mtime=file_mtime,
|
||||
cwd=proj,
|
||||
messages=msgs
|
||||
)
|
||||
for proj, msgs in projects.items()
|
||||
]
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
from .base import BaseParser, SessionData
|
||||
|
||||
class CodexParser(BaseParser):
|
||||
def __init__(self, source: str = 'codex'):
|
||||
self.source = source
|
||||
|
||||
def find_files(self, paths: list) -> list:
|
||||
files = []
|
||||
for base in paths:
|
||||
if not os.path.exists(base):
|
||||
continue
|
||||
for root, _, names in os.walk(base):
|
||||
for f in names:
|
||||
if f.endswith('.jsonl') and f != 'history.jsonl':
|
||||
files.append(os.path.join(root, f))
|
||||
return files
|
||||
|
||||
def _extract_id(self, filepath: str) -> str:
|
||||
match = re.search(r'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})',
|
||||
os.path.basename(filepath))
|
||||
return match.group(1) if match else os.path.basename(filepath).replace('.jsonl', '')
|
||||
|
||||
def parse_file(self, filepath: str) -> SessionData:
|
||||
s = SessionData(
|
||||
session_id=self._extract_id(filepath),
|
||||
source=self.source,
|
||||
file_path=filepath,
|
||||
file_mtime=os.path.getmtime(filepath)
|
||||
)
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line[0] != '{':
|
||||
continue
|
||||
try:
|
||||
data = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
if data.get('type') == 'session_meta':
|
||||
p = data.get('payload', {})
|
||||
s.cwd = p.get('cwd')
|
||||
s.session_id = p.get('id', s.session_id)
|
||||
continue
|
||||
|
||||
if data.get('type') != 'response_item':
|
||||
continue
|
||||
payload = data.get('payload', {})
|
||||
if payload.get('type') != 'message':
|
||||
continue
|
||||
role = payload.get('role')
|
||||
if role not in ('user', 'assistant'):
|
||||
continue
|
||||
|
||||
parts = [item.get('text', '') for item in payload.get('content', [])
|
||||
if isinstance(item, dict) and item.get('type') in ('input_text', 'output_text', 'text')]
|
||||
if parts:
|
||||
s.messages.append({
|
||||
'time': data.get('timestamp', ''),
|
||||
'role': 'user' if role == 'user' else 'ai',
|
||||
'content': ' '.join(parts)
|
||||
})
|
||||
|
||||
return s
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import glob
|
||||
import json
|
||||
from .base import BaseParser, SessionData
|
||||
|
||||
class GeminiParser(BaseParser):
|
||||
def find_files(self, paths: list) -> list:
|
||||
files = []
|
||||
for base in paths:
|
||||
if os.path.exists(base):
|
||||
files.extend(glob.glob(os.path.join(base, "*", "chats", "*.json")))
|
||||
return files
|
||||
|
||||
def parse_file(self, filepath: str) -> SessionData:
|
||||
s = SessionData(
|
||||
session_id=os.path.basename(filepath).replace('.json', ''),
|
||||
source='gemini',
|
||||
file_path=filepath,
|
||||
file_mtime=os.path.getmtime(filepath)
|
||||
)
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
s.session_id = data.get('sessionId', s.session_id)
|
||||
|
||||
for msg in data.get('messages', []):
|
||||
if msg.get('type') not in ('user', 'gemini'):
|
||||
continue
|
||||
content = msg.get('content', '')
|
||||
if content:
|
||||
s.messages.append({
|
||||
'time': msg.get('timestamp', ''),
|
||||
'role': 'user' if msg.get('type') == 'user' else 'ai',
|
||||
'content': content
|
||||
})
|
||||
|
||||
return s
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Kiro CLI 解析器 - 从 SQLite 数据库读取"""
|
||||
import os
|
||||
import json
|
||||
import sqlite3
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from .base import BaseParser, SessionData
|
||||
|
||||
KIRO_DB = os.path.expanduser("~/.local/share/kiro-cli/data.sqlite3")
|
||||
|
||||
class KiroParser(BaseParser):
|
||||
def find_files(self, paths: list) -> list:
|
||||
"""返回数据库路径(如果存在)"""
|
||||
if os.path.exists(KIRO_DB):
|
||||
return [KIRO_DB]
|
||||
return []
|
||||
|
||||
def parse_file(self, filepath: str) -> list:
|
||||
"""解析 Kiro SQLite 数据库,返回多个 SessionData"""
|
||||
sessions = []
|
||||
file_mtime = os.path.getmtime(filepath)
|
||||
|
||||
conn = sqlite3.connect(filepath)
|
||||
for row in conn.execute('SELECT key, value FROM conversations'):
|
||||
cwd, value = row
|
||||
try:
|
||||
data = json.loads(value)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
conv_id = data.get('conversation_id', hashlib.md5(cwd.encode()).hexdigest()[:12])
|
||||
history = data.get('history', [])
|
||||
|
||||
messages = []
|
||||
for item in history:
|
||||
# 用户消息
|
||||
if 'user' in item:
|
||||
user = item['user']
|
||||
content = user.get('content', {})
|
||||
if isinstance(content, dict) and 'Prompt' in content:
|
||||
prompt = content['Prompt'].get('prompt', '')
|
||||
if prompt:
|
||||
messages.append({
|
||||
'time': '',
|
||||
'role': 'user',
|
||||
'content': prompt
|
||||
})
|
||||
|
||||
# AI 回复
|
||||
if 'assistant' in item:
|
||||
assistant = item['assistant']
|
||||
content = assistant.get('content', {})
|
||||
if isinstance(content, dict) and 'Message' in content:
|
||||
msg = content['Message'].get('message', '')
|
||||
if msg:
|
||||
messages.append({
|
||||
'time': '',
|
||||
'role': 'ai',
|
||||
'content': msg
|
||||
})
|
||||
|
||||
if messages:
|
||||
sessions.append(SessionData(
|
||||
session_id=f'kiro-{conv_id[:12]}',
|
||||
source='kiro',
|
||||
file_path=f'kiro:{conv_id}',
|
||||
file_mtime=file_mtime,
|
||||
cwd=cwd,
|
||||
messages=messages
|
||||
))
|
||||
|
||||
conn.close()
|
||||
return sessions
|
||||
Reference in New Issue
Block a user