fluxy-bot 0.8.9 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -84
- package/bin/cli.js +1717 -6
- package/components.json +18 -18
- package/package.json +98 -109
- package/postcss.config.js +3 -3
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/postinstall.js +47 -58
- package/shared/ai.ts +118 -202
- package/shared/config.ts +37 -39
- package/shared/logger.ts +6 -14
- package/shared/paths.ts +10 -16
- package/shared/relay.ts +89 -98
- package/supervisor/backend.ts +77 -89
- package/supervisor/chat/ARCHITECTURE.md +13 -14
- package/supervisor/chat/OnboardWizard.tsx +2194 -3419
- package/supervisor/chat/fluxy-main.tsx +495 -633
- package/supervisor/chat/fluxy.html +24 -36
- package/supervisor/chat/onboard-main.tsx +8 -8
- package/supervisor/chat/onboard.html +10 -13
- package/supervisor/chat/src/components/Chat/AudioBubble.tsx +94 -108
- package/supervisor/chat/src/components/Chat/ChatView.tsx +14 -36
- package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +71 -89
- package/supervisor/chat/src/components/Chat/InputBar.tsx +498 -728
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +154 -246
- package/supervisor/chat/src/components/Chat/MessageList.tsx +114 -138
- package/supervisor/chat/src/components/Chat/TypingIndicator.tsx +38 -50
- package/supervisor/chat/src/components/LoginScreen.tsx +245 -296
- package/supervisor/chat/src/hooks/useChat.ts +210 -259
- package/supervisor/chat/src/hooks/useFluxyChat.ts +232 -322
- package/supervisor/chat/src/hooks/useSpeechRecognition.ts +126 -148
- package/supervisor/chat/src/lib/auth.ts +20 -23
- package/supervisor/chat/src/lib/ws-client.ts +113 -125
- package/supervisor/chat/src/styles/globals.css +76 -85
- package/supervisor/file-saver.ts +39 -52
- package/supervisor/fluxy-agent.ts +213 -292
- package/supervisor/index.ts +895 -1263
- package/supervisor/scheduler.ts +237 -299
- package/supervisor/tunnel.ts +113 -167
- package/supervisor/vite-dev.ts +51 -61
- package/supervisor/widget.js +135 -147
- package/supervisor/worker.ts +45 -49
- package/tsconfig.json +18 -24
- package/vite.config.ts +48 -55
- package/vite.fluxy.config.ts +34 -34
- package/worker/claude-auth.ts +202 -265
- package/worker/codex-auth.ts +144 -181
- package/worker/db.ts +63 -191
- package/worker/index.ts +601 -842
- package/workspace/MYSELF.md +1 -1
- package/workspace/PULSE.json +6 -6
- package/workspace/backend/index.ts +16 -19
- package/workspace/client/index.html +47 -60
- package/workspace/client/public/manifest.json +21 -21
- package/workspace/client/public/sw.js +29 -31
- package/workspace/client/src/App.tsx +91 -136
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +68 -72
- package/workspace/client/src/components/ErrorBoundary.tsx +11 -11
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +35 -35
- package/workspace/client/src/components/Layout/Footer.tsx +12 -12
- package/workspace/client/src/components/Layout/MobileNav.tsx +23 -23
- package/workspace/client/src/components/Layout/Sidebar.tsx +81 -81
- package/workspace/client/src/components/ui/avatar.tsx +85 -85
- package/workspace/client/src/components/ui/badge.tsx +40 -41
- package/workspace/client/src/components/ui/button.tsx +56 -57
- package/workspace/client/src/components/ui/card.tsx +75 -75
- package/workspace/client/src/components/ui/dialog.tsx +114 -117
- package/workspace/client/src/components/ui/dropdown-menu.tsx +191 -199
- package/workspace/client/src/components/ui/input.tsx +17 -17
- package/workspace/client/src/components/ui/scroll-area.tsx +47 -47
- package/workspace/client/src/components/ui/select.tsx +145 -151
- package/workspace/client/src/components/ui/separator.tsx +21 -21
- package/workspace/client/src/components/ui/sheet.tsx +102 -102
- package/workspace/client/src/components/ui/skeleton.tsx +10 -10
- package/workspace/client/src/components/ui/switch.tsx +26 -26
- package/workspace/client/src/components/ui/tabs.tsx +69 -69
- package/workspace/client/src/components/ui/textarea.tsx +14 -14
- package/workspace/client/src/components/ui/tooltip.tsx +37 -37
- package/workspace/client/src/lib/utils.ts +1 -1
- package/workspace/client/src/main.tsx +3 -3
- package/workspace/client/src/styles/globals.css +76 -85
- package/workspace/skills/code-reviewer/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/code-reviewer/skills/code-reviewer/SKILL.md +0 -6
- package/workspace/skills/daily-standup/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/daily-standup/skills/daily-standup/SKILL.md +0 -7
- package/workspace/skills/workspace-helper/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/workspace-helper/skills/workspace-helper/SKILL.md +0 -2
- package/bin/cli.backup.js +0 -2138
- package/cli/commands/daemon.ts +0 -42
- package/cli/commands/init.ts +0 -32
- package/cli/commands/start.ts +0 -113
- package/cli/commands/tunnel.temp.ts +0 -206
- package/cli/commands/tunnel.ts +0 -227
- package/cli/commands/update.ts +0 -163
- package/cli/core/base-adapter.ts +0 -156
- package/cli/core/cloudflared.ts +0 -113
- package/cli/core/config.ts +0 -73
- package/cli/core/os-detector.ts +0 -43
- package/cli/core/server.ts +0 -109
- package/cli/core/types.ts +0 -15
- package/cli/index.ts +0 -72
- package/cli/platforms/darwin.ts +0 -146
- package/cli/platforms/index.ts +0 -21
- package/cli/platforms/linux.ts +0 -156
- package/cli/platforms/win32.ts +0 -34
- package/cli/utils/ui.ts +0 -37
package/worker/db.ts
CHANGED
|
@@ -53,274 +53,146 @@ CREATE INDEX IF NOT EXISTS idx_td_token ON trusted_devices(token);
|
|
|
53
53
|
let db: Database.Database;
|
|
54
54
|
|
|
55
55
|
export function initDb(): void {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
57
|
+
db = new Database(paths.db);
|
|
58
|
+
db.pragma('journal_mode = WAL');
|
|
59
|
+
db.pragma('foreign_keys = ON');
|
|
60
|
+
db.exec(SCHEMA);
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
db.exec('ALTER TABLE conversations ADD COLUMN session_id TEXT');
|
|
68
|
-
}
|
|
62
|
+
// Migration: add session_id column if missing (existing DBs)
|
|
63
|
+
const cols = db.prepare("PRAGMA table_info(conversations)").all() as { name: string }[];
|
|
64
|
+
if (!cols.some((c) => c.name === 'session_id')) {
|
|
65
|
+
db.exec('ALTER TABLE conversations ADD COLUMN session_id TEXT');
|
|
66
|
+
}
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
db.exec('ALTER TABLE messages ADD COLUMN audio_data TEXT');
|
|
76
|
-
}
|
|
68
|
+
// Migration: add audio_data column if missing (voice messages)
|
|
69
|
+
const msgCols = db.prepare("PRAGMA table_info(messages)").all() as { name: string }[];
|
|
70
|
+
if (!msgCols.some((c) => c.name === 'audio_data')) {
|
|
71
|
+
db.exec('ALTER TABLE messages ADD COLUMN audio_data TEXT');
|
|
72
|
+
}
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
db.exec('ALTER TABLE messages ADD COLUMN attachments TEXT');
|
|
84
|
-
}
|
|
74
|
+
// Migration: add attachments column if missing (persistent file attachments)
|
|
75
|
+
const msgCols2 = db.prepare("PRAGMA table_info(messages)").all() as { name: string }[];
|
|
76
|
+
if (!msgCols2.some((c) => c.name === 'attachments')) {
|
|
77
|
+
db.exec('ALTER TABLE messages ADD COLUMN attachments TEXT');
|
|
78
|
+
}
|
|
85
79
|
}
|
|
86
80
|
|
|
87
|
-
export function closeDb(): void {
|
|
88
|
-
db?.close();
|
|
89
|
-
}
|
|
81
|
+
export function closeDb(): void { db?.close(); }
|
|
90
82
|
|
|
91
83
|
// Conversations
|
|
92
84
|
export function createConversation(title?: string, model?: string) {
|
|
93
|
-
|
|
94
|
-
.prepare(
|
|
95
|
-
'INSERT INTO conversations (title, model) VALUES (?, ?) RETURNING *'
|
|
96
|
-
)
|
|
97
|
-
.get(title ?? null, model ?? null) as any;
|
|
85
|
+
return db.prepare('INSERT INTO conversations (title, model) VALUES (?, ?) RETURNING *').get(title ?? null, model ?? null) as any;
|
|
98
86
|
}
|
|
99
87
|
export function listConversations(limit = 50) {
|
|
100
|
-
|
|
101
|
-
.prepare('SELECT * FROM conversations ORDER BY updated_at DESC LIMIT ?')
|
|
102
|
-
.all(limit);
|
|
88
|
+
return db.prepare('SELECT * FROM conversations ORDER BY updated_at DESC LIMIT ?').all(limit);
|
|
103
89
|
}
|
|
104
90
|
export function deleteConversation(id: string) {
|
|
105
|
-
|
|
91
|
+
db.prepare('DELETE FROM conversations WHERE id = ?').run(id);
|
|
106
92
|
}
|
|
107
93
|
|
|
108
94
|
// Messages
|
|
109
|
-
export function addMessage(
|
|
110
|
-
|
|
111
|
-
role
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
tokens_in?: number;
|
|
115
|
-
tokens_out?: number;
|
|
116
|
-
model?: string;
|
|
117
|
-
audio_data?: string;
|
|
118
|
-
attachments?: string;
|
|
119
|
-
}
|
|
120
|
-
) {
|
|
121
|
-
const msg = db
|
|
122
|
-
.prepare(
|
|
123
|
-
'INSERT INTO messages (conversation_id, role, content, tokens_in, tokens_out, model, audio_data, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *'
|
|
124
|
-
)
|
|
125
|
-
.get(
|
|
126
|
-
convId,
|
|
127
|
-
role,
|
|
128
|
-
content,
|
|
129
|
-
meta?.tokens_in ?? null,
|
|
130
|
-
meta?.tokens_out ?? null,
|
|
131
|
-
meta?.model ?? null,
|
|
132
|
-
meta?.audio_data ?? null,
|
|
133
|
-
meta?.attachments ?? null
|
|
134
|
-
);
|
|
135
|
-
db.prepare(
|
|
136
|
-
'UPDATE conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?'
|
|
137
|
-
).run(convId);
|
|
138
|
-
return msg as any;
|
|
95
|
+
export function addMessage(convId: string, role: string, content: string, meta?: { tokens_in?: number; tokens_out?: number; model?: string; audio_data?: string; attachments?: string }) {
|
|
96
|
+
const msg = db.prepare('INSERT INTO messages (conversation_id, role, content, tokens_in, tokens_out, model, audio_data, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *')
|
|
97
|
+
.get(convId, role, content, meta?.tokens_in ?? null, meta?.tokens_out ?? null, meta?.model ?? null, meta?.audio_data ?? null, meta?.attachments ?? null);
|
|
98
|
+
db.prepare('UPDATE conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(convId);
|
|
99
|
+
return msg as any;
|
|
139
100
|
}
|
|
140
101
|
export function getMessages(convId: string) {
|
|
141
|
-
|
|
142
|
-
.prepare(
|
|
143
|
-
'SELECT * FROM messages WHERE conversation_id = ? ORDER BY created_at ASC'
|
|
144
|
-
)
|
|
145
|
-
.all(convId);
|
|
102
|
+
return db.prepare('SELECT * FROM messages WHERE conversation_id = ? ORDER BY created_at ASC').all(convId);
|
|
146
103
|
}
|
|
147
104
|
|
|
148
105
|
// Settings
|
|
149
106
|
export function getSetting(key: string): string | undefined {
|
|
150
|
-
|
|
151
|
-
db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as any
|
|
152
|
-
)?.value;
|
|
107
|
+
return (db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as any)?.value;
|
|
153
108
|
}
|
|
154
109
|
export function setSetting(key: string, value: string) {
|
|
155
|
-
|
|
156
|
-
'INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP'
|
|
157
|
-
).run(key, value);
|
|
110
|
+
db.prepare('INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP').run(key, value);
|
|
158
111
|
}
|
|
159
112
|
export function getAllSettings() {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
value: string;
|
|
163
|
-
}[];
|
|
164
|
-
return Object.fromEntries(rows.map(r => [r.key, r.value]));
|
|
113
|
+
const rows = db.prepare('SELECT key, value FROM settings').all() as { key: string; value: string }[];
|
|
114
|
+
return Object.fromEntries(rows.map((r) => [r.key, r.value]));
|
|
165
115
|
}
|
|
166
116
|
|
|
167
117
|
// Auth sessions
|
|
168
118
|
export function createSession(token: string, expiresAt: string) {
|
|
169
|
-
|
|
170
|
-
token,
|
|
171
|
-
expiresAt
|
|
172
|
-
);
|
|
119
|
+
db.prepare('INSERT INTO sessions (token, expires_at) VALUES (?, ?)').run(token, expiresAt);
|
|
173
120
|
}
|
|
174
|
-
export function getSession(
|
|
175
|
-
|
|
176
|
-
): { token: string; created_at: string; expires_at: string } | undefined {
|
|
177
|
-
return db
|
|
178
|
-
.prepare(
|
|
179
|
-
"SELECT * FROM sessions WHERE token = ? AND expires_at > datetime('now')"
|
|
180
|
-
)
|
|
181
|
-
.get(token) as any;
|
|
121
|
+
export function getSession(token: string): { token: string; created_at: string; expires_at: string } | undefined {
|
|
122
|
+
return db.prepare('SELECT * FROM sessions WHERE token = ? AND expires_at > datetime(\'now\')').get(token) as any;
|
|
182
123
|
}
|
|
183
124
|
export function deleteSession(token: string) {
|
|
184
|
-
|
|
125
|
+
db.prepare('DELETE FROM sessions WHERE token = ?').run(token);
|
|
185
126
|
}
|
|
186
127
|
export function deleteExpiredSessions() {
|
|
187
|
-
|
|
188
|
-
"DELETE FROM sessions WHERE expires_at <= datetime('now')"
|
|
189
|
-
).run();
|
|
128
|
+
db.prepare('DELETE FROM sessions WHERE expires_at <= datetime(\'now\')').run();
|
|
190
129
|
}
|
|
191
130
|
|
|
192
131
|
// Session ID (Agent SDK)
|
|
193
132
|
export function getSessionId(convId: string): string | null {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
.get(convId) as any;
|
|
197
|
-
return row?.session_id ?? null;
|
|
133
|
+
const row = db.prepare('SELECT session_id FROM conversations WHERE id = ?').get(convId) as any;
|
|
134
|
+
return row?.session_id ?? null;
|
|
198
135
|
}
|
|
199
136
|
export function saveSessionId(convId: string, sessionId: string): void {
|
|
200
|
-
|
|
201
|
-
sessionId,
|
|
202
|
-
convId
|
|
203
|
-
);
|
|
137
|
+
db.prepare('UPDATE conversations SET session_id = ? WHERE id = ?').run(sessionId, convId);
|
|
204
138
|
}
|
|
205
139
|
|
|
206
140
|
// Push subscriptions
|
|
207
|
-
export function addPushSubscription(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
auth: string
|
|
211
|
-
) {
|
|
212
|
-
db.prepare(
|
|
213
|
-
`INSERT INTO push_subscriptions (endpoint, keys_p256dh, keys_auth) VALUES (?, ?, ?)
|
|
141
|
+
export function addPushSubscription(endpoint: string, p256dh: string, auth: string) {
|
|
142
|
+
db.prepare(
|
|
143
|
+
`INSERT INTO push_subscriptions (endpoint, keys_p256dh, keys_auth) VALUES (?, ?, ?)
|
|
214
144
|
ON CONFLICT(endpoint) DO UPDATE SET keys_p256dh = excluded.keys_p256dh, keys_auth = excluded.keys_auth`
|
|
215
|
-
|
|
145
|
+
).run(endpoint, p256dh, auth);
|
|
216
146
|
}
|
|
217
147
|
export function removePushSubscription(endpoint: string) {
|
|
218
|
-
|
|
219
|
-
endpoint
|
|
220
|
-
);
|
|
148
|
+
db.prepare('DELETE FROM push_subscriptions WHERE endpoint = ?').run(endpoint);
|
|
221
149
|
}
|
|
222
150
|
export function getAllPushSubscriptions() {
|
|
223
|
-
|
|
224
|
-
id: number;
|
|
225
|
-
endpoint: string;
|
|
226
|
-
keys_p256dh: string;
|
|
227
|
-
keys_auth: string;
|
|
228
|
-
}[];
|
|
151
|
+
return db.prepare('SELECT * FROM push_subscriptions').all() as { id: number; endpoint: string; keys_p256dh: string; keys_auth: string }[];
|
|
229
152
|
}
|
|
230
153
|
export function getPushSubscriptionByEndpoint(endpoint: string) {
|
|
231
|
-
|
|
232
|
-
.prepare('SELECT * FROM push_subscriptions WHERE endpoint = ?')
|
|
233
|
-
.get(endpoint) as
|
|
234
|
-
| {
|
|
235
|
-
id: number;
|
|
236
|
-
endpoint: string;
|
|
237
|
-
keys_p256dh: string;
|
|
238
|
-
keys_auth: string;
|
|
239
|
-
}
|
|
240
|
-
| undefined;
|
|
154
|
+
return db.prepare('SELECT * FROM push_subscriptions WHERE endpoint = ?').get(endpoint) as { id: number; endpoint: string; keys_p256dh: string; keys_auth: string } | undefined;
|
|
241
155
|
}
|
|
242
156
|
|
|
243
157
|
// Trusted devices (2FA)
|
|
244
|
-
export function createTrustedDevice(
|
|
245
|
-
|
|
246
|
-
label: string,
|
|
247
|
-
expiresAt: string
|
|
248
|
-
) {
|
|
249
|
-
return db
|
|
250
|
-
.prepare(
|
|
251
|
-
'INSERT INTO trusted_devices (token, label, expires_at) VALUES (?, ?, ?) RETURNING *'
|
|
252
|
-
)
|
|
253
|
-
.get(token, label, expiresAt) as any;
|
|
158
|
+
export function createTrustedDevice(token: string, label: string, expiresAt: string) {
|
|
159
|
+
return db.prepare('INSERT INTO trusted_devices (token, label, expires_at) VALUES (?, ?, ?) RETURNING *').get(token, label, expiresAt) as any;
|
|
254
160
|
}
|
|
255
|
-
export function getTrustedDevice(token: string):
|
|
256
|
-
|
|
257
|
-
id: string;
|
|
258
|
-
token: string;
|
|
259
|
-
label: string;
|
|
260
|
-
created_at: string;
|
|
261
|
-
expires_at: string;
|
|
262
|
-
last_seen: string;
|
|
263
|
-
}
|
|
264
|
-
| undefined {
|
|
265
|
-
return db
|
|
266
|
-
.prepare(
|
|
267
|
-
"SELECT * FROM trusted_devices WHERE token = ? AND expires_at > datetime('now')"
|
|
268
|
-
)
|
|
269
|
-
.get(token) as any;
|
|
161
|
+
export function getTrustedDevice(token: string): { id: string; token: string; label: string; created_at: string; expires_at: string; last_seen: string } | undefined {
|
|
162
|
+
return db.prepare("SELECT * FROM trusted_devices WHERE token = ? AND expires_at > datetime('now')").get(token) as any;
|
|
270
163
|
}
|
|
271
164
|
export function updateDeviceLastSeen(token: string) {
|
|
272
|
-
|
|
273
|
-
'UPDATE trusted_devices SET last_seen = CURRENT_TIMESTAMP WHERE token = ?'
|
|
274
|
-
).run(token);
|
|
165
|
+
db.prepare("UPDATE trusted_devices SET last_seen = CURRENT_TIMESTAMP WHERE token = ?").run(token);
|
|
275
166
|
}
|
|
276
167
|
export function listTrustedDevices() {
|
|
277
|
-
|
|
278
|
-
.prepare(
|
|
279
|
-
"SELECT id, label, last_seen, created_at FROM trusted_devices WHERE expires_at > datetime('now') ORDER BY last_seen DESC"
|
|
280
|
-
)
|
|
281
|
-
.all() as {
|
|
282
|
-
id: string;
|
|
283
|
-
label: string;
|
|
284
|
-
last_seen: string;
|
|
285
|
-
created_at: string;
|
|
286
|
-
}[];
|
|
168
|
+
return db.prepare("SELECT id, label, last_seen, created_at FROM trusted_devices WHERE expires_at > datetime('now') ORDER BY last_seen DESC").all() as { id: string; label: string; last_seen: string; created_at: string }[];
|
|
287
169
|
}
|
|
288
170
|
export function deleteTrustedDevice(id: string) {
|
|
289
|
-
|
|
171
|
+
db.prepare('DELETE FROM trusted_devices WHERE id = ?').run(id);
|
|
290
172
|
}
|
|
291
173
|
export function deleteExpiredDevices() {
|
|
292
|
-
|
|
293
|
-
"DELETE FROM trusted_devices WHERE expires_at <= datetime('now')"
|
|
294
|
-
).run();
|
|
174
|
+
db.prepare("DELETE FROM trusted_devices WHERE expires_at <= datetime('now')").run();
|
|
295
175
|
}
|
|
296
176
|
export function deleteAllTrustedDevices() {
|
|
297
|
-
|
|
177
|
+
db.prepare('DELETE FROM trusted_devices').run();
|
|
298
178
|
}
|
|
299
179
|
|
|
300
180
|
// Recent messages (for context injection)
|
|
301
181
|
export function getRecentMessages(convId: string, limit = 20) {
|
|
302
|
-
|
|
303
|
-
.prepare(`
|
|
182
|
+
return db.prepare(`
|
|
304
183
|
SELECT * FROM (
|
|
305
184
|
SELECT * FROM messages WHERE conversation_id = ? ORDER BY created_at DESC LIMIT ?
|
|
306
185
|
) sub ORDER BY created_at ASC
|
|
307
|
-
`)
|
|
308
|
-
.all(convId, limit);
|
|
186
|
+
`).all(convId, limit);
|
|
309
187
|
}
|
|
310
188
|
|
|
311
189
|
// Cursor-based pagination: messages before a given ID
|
|
312
|
-
export function getMessagesBefore(
|
|
313
|
-
|
|
314
|
-
beforeId: string,
|
|
315
|
-
limit = 20
|
|
316
|
-
) {
|
|
317
|
-
return db
|
|
318
|
-
.prepare(`
|
|
190
|
+
export function getMessagesBefore(convId: string, beforeId: string, limit = 20) {
|
|
191
|
+
return db.prepare(`
|
|
319
192
|
SELECT * FROM (
|
|
320
193
|
SELECT * FROM messages
|
|
321
194
|
WHERE conversation_id = ? AND id < ?
|
|
322
195
|
ORDER BY id DESC LIMIT ?
|
|
323
196
|
) sub ORDER BY id ASC
|
|
324
|
-
`)
|
|
325
|
-
.all(convId, beforeId, limit);
|
|
197
|
+
`).all(convId, beforeId, limit);
|
|
326
198
|
}
|