forked from karpathy/reader3
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdatabase.py
More file actions
251 lines (202 loc) · 8.11 KB
/
database.py
File metadata and controls
251 lines (202 loc) · 8.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
"""
Database models for storing highlights and AI interactions.
"""
import sqlite3
import json
from datetime import datetime
from typing import List, Dict, Optional
from dataclasses import dataclass, asdict
@dataclass
class Highlight:
"""User highlight with position info."""
id: Optional[int] = None
book_id: str = ""
chapter_index: int = 0
selected_text: str = ""
context_before: str = ""
context_after: str = ""
created_at: str = ""
@dataclass
class AIAnalysis:
"""AI analysis result (fact-check or discussion)."""
id: Optional[int] = None
highlight_id: int = 0
analysis_type: str = "" # 'fact_check' or 'discussion'
prompt: str = ""
response: str = ""
created_at: str = ""
class Database:
"""Simple SQLite database for storing highlights and AI analyses."""
def __init__(self, db_path: str = "reader_data.db"):
self.db_path = db_path
self.init_db()
def init_db(self):
"""Create tables if they don't exist."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS highlights (
id INTEGER PRIMARY KEY AUTOINCREMENT,
book_id TEXT NOT NULL,
chapter_index INTEGER NOT NULL,
selected_text TEXT NOT NULL,
context_before TEXT,
context_after TEXT,
created_at TEXT NOT NULL
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS ai_analyses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
highlight_id INTEGER NOT NULL,
analysis_type TEXT NOT NULL,
prompt TEXT NOT NULL,
response TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY (highlight_id) REFERENCES highlights (id)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS reading_progress (
id INTEGER PRIMARY KEY AUTOINCREMENT,
book_id TEXT NOT NULL UNIQUE,
chapter_index INTEGER NOT NULL,
scroll_position INTEGER DEFAULT 0,
last_read_at TEXT NOT NULL
)
""")
conn.commit()
conn.close()
def save_highlight(self, highlight: Highlight) -> int:
"""Save a highlight and return its ID."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO highlights (book_id, chapter_index, selected_text,
context_before, context_after, created_at)
VALUES (?, ?, ?, ?, ?, ?)
""", (
highlight.book_id,
highlight.chapter_index,
highlight.selected_text,
highlight.context_before,
highlight.context_after,
highlight.created_at or datetime.now().isoformat()
))
highlight_id = cursor.lastrowid
conn.commit()
conn.close()
return highlight_id
def save_analysis(self, analysis: AIAnalysis) -> int:
"""Save an AI analysis and return its ID."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO ai_analyses (highlight_id, analysis_type, prompt, response, created_at)
VALUES (?, ?, ?, ?, ?)
""", (
analysis.highlight_id,
analysis.analysis_type,
analysis.prompt,
analysis.response,
analysis.created_at or datetime.now().isoformat()
))
analysis_id = cursor.lastrowid
conn.commit()
conn.close()
return analysis_id
def get_highlights_for_chapter(self, book_id: str, chapter_index: int) -> List[Dict]:
"""Get all highlights for a specific chapter."""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM highlights
WHERE book_id = ? AND chapter_index = ?
ORDER BY created_at DESC
""", (book_id, chapter_index))
rows = cursor.fetchall()
conn.close()
return [dict(row) for row in rows]
def get_all_highlights_for_book(self, book_id: str) -> List[Dict]:
"""Get all highlights for a book (all chapters)."""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM highlights
WHERE book_id = ?
ORDER BY created_at DESC
""", (book_id,))
rows = cursor.fetchall()
conn.close()
return [dict(row) for row in rows]
def get_analyses_for_highlight(self, highlight_id: int) -> List[Dict]:
"""Get all AI analyses for a highlight."""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM ai_analyses
WHERE highlight_id = ?
ORDER BY created_at DESC
""", (highlight_id,))
rows = cursor.fetchall()
conn.close()
return [dict(row) for row in rows]
def update_analysis(self, analysis_id: int, response: str):
"""Update an existing analysis response (for editing comments)."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
UPDATE ai_analyses
SET response = ?
WHERE id = ?
""", (response, analysis_id))
conn.commit()
conn.close()
def delete_analysis(self, analysis_id: int):
"""Delete an analysis and its highlight if no other analyses exist."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Get the highlight_id before deleting
cursor.execute("SELECT highlight_id FROM ai_analyses WHERE id = ?", (analysis_id,))
result = cursor.fetchone()
if result:
highlight_id = result[0]
# Delete the analysis
cursor.execute("DELETE FROM ai_analyses WHERE id = ?", (analysis_id,))
# Check if there are other analyses for this highlight
cursor.execute("SELECT COUNT(*) FROM ai_analyses WHERE highlight_id = ?", (highlight_id,))
count = cursor.fetchone()[0]
# If no other analyses, delete the highlight too
if count == 0:
cursor.execute("DELETE FROM highlights WHERE id = ?", (highlight_id,))
conn.commit()
conn.close()
def save_progress(self, book_id: str, chapter_index: int, scroll_position: int = 0):
"""Save or update reading progress for a book."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO reading_progress (book_id, chapter_index, scroll_position, last_read_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(book_id) DO UPDATE SET
chapter_index = excluded.chapter_index,
scroll_position = excluded.scroll_position,
last_read_at = excluded.last_read_at
""", (book_id, chapter_index, scroll_position, datetime.now().isoformat()))
conn.commit()
conn.close()
def get_progress(self, book_id: str) -> Optional[Dict]:
"""Get the last read position for a book."""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("""
SELECT chapter_index, scroll_position FROM reading_progress
WHERE book_id = ?
""", (book_id,))
result = cursor.fetchone()
conn.close()
return dict(result) if result else None