Skip to content

Commit 47e3ca6

Browse files
committed
UI/UX overhaul, tool result classification, and bug fixes
1 parent 9b44027 commit 47e3ca6

10 files changed

Lines changed: 1266 additions & 131 deletions

File tree

README.md

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,65 @@ Browse and export Claude Code chat history — Web GUI and CLI.
44

55
## Features
66

7-
- **Web GUI**: Flask-based browser with project list, session viewer, full-text search, syntax highlighting, tool call rendering, thinking blocks, dark/light mode
8-
- **CLI Export**: Standalone script to export all sessions to Markdown with YAML frontmatter
9-
- **Rich Markdown**: Includes token usage, tool calls (Bash, Read, Edit, Write, Glob, Grep, Task, etc.), thinking blocks, model info, timestamps
10-
- **Incremental Export**: `--since last` flag to only export new/updated sessions
11-
- **Bulk Export**: Download all sessions as a zip from the web UI
7+
### Web GUI
8+
- **Project dashboard** with card grid, aggregate stats, and staggered animations
9+
- **Session viewer** with split layout (sidebar + message panel)
10+
- **Full-text search** across all sessions
11+
- **Syntax highlighting** for code blocks
12+
- **Tool call rendering** — Bash, Read, Edit, Write, Glob, Grep, Task, TodoWrite, WebFetch, WebSearch, and more
13+
- **Thinking blocks** — collapsible sections for Claude's reasoning
14+
- **Dark/light theme** with Inter font
15+
- **Responsive design** — mobile-friendly with hamburger sidebar
16+
- **Toast notifications** with icon, progress bar, and close button
17+
- **Confirm modals** with keyboard support (Enter/Escape) and backdrop blur
18+
- **Top loading bar** (YouTube-style) during data fetches
19+
- **Smooth transitions** — staggered card/message animations, crossfade content swaps
20+
- **Scroll-to-top button** in bottom-right corner
21+
- **Per-model badges** in session header
22+
- **Bulk export** — download all sessions as a zip
23+
24+
### CLI Export
25+
- Standalone script to export all sessions to Markdown with YAML frontmatter
26+
- Rich Markdown: token usage, tool calls, thinking blocks, model info, timestamps
27+
- `--since last` flag for incremental export (only new/updated sessions)
28+
- `--project` flag to export a specific project
1229

1330
## Quick Start
1431

1532
### Web GUI
1633

1734
```bash
18-
pip install flask
35+
# Create virtual environment
36+
python -m venv venv
37+
38+
# Activate (Windows)
39+
venv\Scripts\activate
40+
41+
# Activate (macOS/Linux)
42+
source venv/bin/activate
43+
44+
# Install dependencies
45+
pip install -r requirements.txt
46+
47+
# Run
1948
python app.py
20-
# Open http://localhost:3000
49+
# Open http://localhost:5000
50+
```
51+
52+
Options:
53+
```bash
54+
python app.py --port 8080 --host 0.0.0.0
55+
python app.py --base-dir /path/to/claude/projects
2156
```
2257

2358
### CLI Export
2459

2560
```bash
61+
# Activate venv first (see above), then:
62+
63+
# List all projects (shows directory names you can use with --project)
64+
python scripts/export.py list
65+
2666
# Export all sessions as zip
2767
python scripts/export.py
2868

@@ -32,10 +72,12 @@ python scripts/export.py --out ./exports --no-zip
3272
# Incremental export (only new sessions since last run)
3373
python scripts/export.py --since last
3474

35-
# Export specific project only
36-
python scripts/export.py --project myproject
75+
# Export specific project only (substring match on directory name)
76+
python scripts/export.py --project boost-capy
3777
```
3878

79+
The `--project` flag matches against the directory names under `~/.claude/projects/`. These are path-based names like `F--boost-capy` or `d--harbor-forge`. You can use any substring — for example `boost-capy` will match `F--boost-capy`. Run `python scripts/export.py list` to see all available project names.
80+
3981
## Data Source
4082

4183
Reads from `~/.claude/projects/` which contains JSONL session files created by Claude Code.
@@ -46,22 +88,22 @@ Reads from `~/.claude/projects/` which contains JSONL session files created by C
4688

4789
```
4890
claude-code-chat-browser/
49-
├── app.py # Flask entry point
91+
├── app.py # Flask entry point (default port 5000)
5092
├── api/
51-
│ ├── projects.py # Project listing endpoints
52-
│ ├── sessions.py # Session viewer endpoints
53-
│ ├── search.py # Full-text search
54-
│ └── export_api.py # Bulk and per-session export
93+
│ ├── projects.py # Project listing & session counts
94+
│ ├── sessions.py # Session parsing & message delivery
95+
│ ├── search.py # Full-text search across sessions
96+
│ └── export_api.py # Bulk zip and per-session Markdown export
5597
├── utils/
56-
│ ├── session_path.py # OS-aware path detection
57-
│ ├── jsonl_parser.py # JSONL session parser
58-
│ └── md_exporter.py # Markdown exporter with frontmatter
98+
│ ├── session_path.py # OS-aware path detection & project naming
99+
│ ├── jsonl_parser.py # JSONL session parser with tool result classification
100+
│ └── md_exporter.py # Markdown exporter with YAML frontmatter
59101
├── scripts/
60-
│ └── export.py # Standalone CLI export
102+
│ └── export.py # Standalone CLI export tool
61103
├── static/
62-
│ ├── index.html # SPA entry point
63-
│ ├── css/style.css # Dark/light theme
64-
│ └── js/app.js # Client-side routing and rendering
104+
│ ├── index.html # SPA entry point (Inter font, minimal markup)
105+
│ ├── css/style.css # Dark/light theme, responsive, animations
106+
│ └── js/app.js # Hash-based routing, rendering, UI components
65107
└── tests/
66108
```
67109

@@ -72,5 +114,5 @@ Each exported session includes:
72114
- **YAML frontmatter**: title, timestamps, session_id, models, token counts, tool call breakdown, working directory, git branch, Claude Code version
73115
- **Per-message metadata**: role, model, token usage (in/out/cache), timestamp
74116
- **Thinking blocks**: Collapsible `<details>` sections
75-
- **Tool calls**: Formatted by type (Bash commands, file reads/edits, glob/grep patterns, subagent tasks, todos)
117+
- **Tool calls**: Formatted by type (Bash commands, file reads/edits, glob/grep patterns, subagent tasks, todos, web fetches, plans)
76118
- **System events**: Context compaction markers

api/projects.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Project listing endpoints."""
22

3+
import traceback
4+
35
from flask import Blueprint, current_app, jsonify
46

57
from utils.session_path import get_claude_projects_dir, list_projects, list_sessions, safe_join
@@ -11,6 +13,30 @@
1113
def get_projects():
1214
base = current_app.config.get("CLAUDE_PROJECTS_DIR") or get_claude_projects_dir()
1315
projects = list_projects(base)
16+
17+
# Enrich each project with accurate titled-session count and latest timestamp
18+
# so the landing page matches what the workspace page shows.
19+
# Uses quick_session_info() which peeks at files without full parsing.
20+
from utils.jsonl_parser import quick_session_info
21+
for project in projects:
22+
sessions = list_sessions(project["path"])
23+
titled_count = 0
24+
latest_ts = None
25+
for s in sessions:
26+
try:
27+
info = quick_session_info(s["path"])
28+
if info["title"] == "Untitled Session":
29+
continue
30+
titled_count += 1
31+
ts = info.get("last_timestamp") or info.get("first_timestamp")
32+
if ts and (latest_ts is None or ts > latest_ts):
33+
latest_ts = ts
34+
except Exception:
35+
titled_count += 1
36+
project["session_count"] = titled_count
37+
if latest_ts:
38+
project["last_modified"] = latest_ts
39+
1440
return jsonify(projects)
1541

1642

@@ -42,5 +68,6 @@ def get_project_sessions(project_name):
4268
"last_timestamp": meta["last_timestamp"],
4369
})
4470
except Exception as e:
45-
result.append({**s, "title": "Error parsing session", "error": True, "error_detail": str(e)})
71+
print(f"[ERROR] Failed to parse {s['id']}: {type(e).__name__}: {e}\n{traceback.format_exc()}")
72+
result.append({**s, "title": "Error parsing session", "error": True, "error_detail": f"{type(e).__name__}: {e}"})
4673
return jsonify(result)

api/sessions.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Session detail and stats endpoints."""
22

33
import os
4+
import traceback
45

56
from flask import Blueprint, current_app, jsonify, abort
67

@@ -17,13 +18,20 @@ def get_session(project_name, session_id):
1718
try:
1819
filepath = safe_join(base, project_name, f"{session_id}.jsonl")
1920
except ValueError:
20-
abort(400, description="Invalid path")
21+
return jsonify({"error": "Invalid path"}), 400
2122

2223
if not os.path.isfile(filepath):
23-
abort(404, description=f"Session {session_id} not found")
24+
return jsonify({"error": f"Session {session_id} not found"}), 404
2425

25-
session = parse_session(filepath)
26-
return jsonify(session)
26+
try:
27+
session = parse_session(filepath)
28+
return jsonify(session)
29+
except Exception as e:
30+
tb = traceback.format_exc()
31+
print(f"[ERROR] Failed to parse session {session_id}: {e}\n{tb}")
32+
return jsonify({
33+
"error": f"Failed to parse session: {type(e).__name__}: {e}",
34+
}), 500
2735

2836

2937
@sessions_bp.route("/api/sessions/<path:project_name>/<session_id>/stats")
@@ -32,11 +40,18 @@ def get_session_stats(project_name, session_id):
3240
try:
3341
filepath = safe_join(base, project_name, f"{session_id}.jsonl")
3442
except ValueError:
35-
abort(400, description="Invalid path")
43+
return jsonify({"error": "Invalid path"}), 400
3644

3745
if not os.path.isfile(filepath):
38-
abort(404, description=f"Session {session_id} not found")
46+
return jsonify({"error": f"Session {session_id} not found"}), 404
3947

40-
session = parse_session(filepath)
41-
stats = compute_stats(session)
42-
return jsonify(stats)
48+
try:
49+
session = parse_session(filepath)
50+
stats = compute_stats(session)
51+
return jsonify(stats)
52+
except Exception as e:
53+
tb = traceback.format_exc()
54+
print(f"[ERROR] Failed to compute stats for {session_id}: {e}\n{tb}")
55+
return jsonify({
56+
"error": f"Failed to compute stats: {type(e).__name__}: {e}",
57+
}), 500

app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ def index():
3030
import argparse
3131

3232
parser = argparse.ArgumentParser(description="Claude Code Chat Browser")
33-
parser.add_argument("--port", type=int, default=3000)
33+
parser.add_argument("--port", type=int, default=5000)
3434
parser.add_argument("--host", default="127.0.0.1")
3535
parser.add_argument("--base-dir", default=None, help="Override Claude projects dir")
3636
args = parser.parse_args()
3737

3838
app = create_app(base_dir=args.base_dir)
3939
print(f"Claude Code Chat Browser running at http://{args.host}:{args.port}")
40-
app.run(host=args.host, port=args.port, debug=True)
40+
app.run(host=args.host, port=args.port, debug=True, use_reloader=False)

0 commit comments

Comments
 (0)