Skip to content

Commit 1c58911

Browse files
committed
WIP on ai-agent: cf8bac5 Merge remote-tracking branch 'upstream/ai-agent' into ai-agent
2 parents cf8bac5 + e6e221b commit 1c58911

File tree

3 files changed

+124
-7
lines changed

3 files changed

+124
-7
lines changed

src/pages/aiAssistant/assistant.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { isAIMessageChunk } from "@langchain/core/messages";
22
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
33
import { MemorySaver } from "@langchain/langgraph-checkpoint";
4+
import { CordovaSqliteCheckpointSaver } from "./checkpoint";
45
import { createReactAgent } from "@langchain/langgraph/prebuilt";
56
import confirm from "dialogs/confirm";
67
import select from "dialogs/select";
@@ -36,12 +37,12 @@ export default function openAIAssistantPage() {
3637
let currentController = null;
3738
let aiTabInstance;
3839

39-
const GEMINI_API_KEY = ""; // Replace
40+
const GEMINI_API_KEY = "AIzaSyBuDDGk2HM5bH7TwuQwEvMeLvPtlFVCKsQ"; // Replace
4041

4142
const searchTool = {
4243
googleSearch: {},
4344
};
44-
const agentCheckpointer = new MemorySaver();
45+
const agentCheckpointer = new CordovaSqliteCheckpointSaver();
4546
const model = new ChatGoogleGenerativeAI({
4647
model: "gemini-2.0-flash",
4748
apiKey: GEMINI_API_KEY,

src/pages/aiAssistant/db.js

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// src/pages/aiAssistant/db.js (or a more general lib folder)
2-
31
const DB_NAME = "AIAssistant.db"; // SQLite convention for .db extension
42
const DB_LOCATION = "default"; // Standard location
53

@@ -61,13 +59,27 @@ function initializeTables() {
6159
FOREIGN KEY (conversationId) REFERENCES conversations(id) ON DELETE CASCADE
6260
)
6361
`);
64-
// Index for faster querying of messages by conversationId and sorting
62+
// LangGraph Checkpoints Table
63+
tx.executeSql(`
64+
CREATE TABLE IF NOT EXISTS langgraph_checkpoints (
65+
thread_id TEXT NOT NULL,
66+
checkpoint_id TEXT NOT NULL,
67+
parent_checkpoint_id TEXT,
68+
checkpoint TEXT,
69+
updated_at INTEGER,
70+
PRIMARY KEY (thread_id, checkpoint_id)
71+
)
72+
`);
73+
// Indexes
6574
tx.executeSql(
6675
`CREATE INDEX IF NOT EXISTS idx_messages_conversationId_timestamp ON messages (conversationId, timestamp)`,
6776
);
6877
tx.executeSql(
6978
`CREATE INDEX IF NOT EXISTS idx_conversations_lastModifiedAt ON conversations (lastModifiedAt)`,
7079
);
80+
tx.executeSql(
81+
`CREATE INDEX IF NOT EXISTS idx_lg_checkpoints_thread_id_updated_at ON langgraph_checkpoints (thread_id, updated_at DESC)`);
82+
7183
},
7284
(error) => {
7385
console.error(
@@ -264,4 +276,108 @@ export async function deleteConversation(conversationId) {
264276
});
265277
}
266278

267-
// Ensure DB is opened and tables initialized when module loads or on first call
279+
// --- LangGraph Checkpoint DB Functions ---
280+
export async function getLangGraphCheckpointFromDB(threadId, checkpointId = null) {
281+
await openDB();
282+
return new Promise((resolve, reject) => {
283+
db.readTransaction(async (tx) => {
284+
try {
285+
let sql = "SELECT checkpoint FROM langgraph_checkpoints WHERE thread_id = ?";
286+
const params = [threadId];
287+
if (checkpointId) {
288+
sql += " AND checkpoint_id = ?";
289+
params.push(checkpointId);
290+
} else {
291+
sql += " ORDER BY updated_at DESC LIMIT 1"; // Get latest for the thread
292+
}
293+
const resultSet = await executeSqlAsync(tx, sql, params);
294+
if (resultSet.rows.length > 0) {
295+
const checkpointStr = resultSet.rows.item(0).checkpoint;
296+
resolve(checkpointStr ? JSON.parse(checkpointStr) : null);
297+
} else {
298+
resolve(null);
299+
}
300+
} catch (error) {
301+
console.error(`[DB] Error getting LangGraph checkpoint (thread: ${threadId}, ckpt: ${checkpointId}):`, error);
302+
reject(error);
303+
}
304+
});
305+
});
306+
}
307+
308+
export async function putLangGraphCheckpointInDB(threadId, checkpointTuple) {
309+
await openDB();
310+
const checkpointId = checkpointTuple?.config?.configurable?.checkpoint_id;
311+
const parentCheckpointId = checkpointTuple?.parent_config?.configurable?.checkpoint_id || null;
312+
313+
if (!checkpointId) {
314+
console.error("[DB] Cannot save LangGraph checkpoint: checkpoint_id missing from checkpointTuple.config");
315+
return Promise.reject(new Error("Missing checkpoint_id in checkpoint config"));
316+
}
317+
const checkpointStr = JSON.stringify(checkpointTuple);
318+
const updatedAt = Date.parse(checkpointTuple.config?.configurable?.checkpoint_id?.split("T")[0] || checkpointTuple.ts || new Date().toISOString());
319+
320+
321+
return new Promise((resolve, reject) => {
322+
db.transaction(async (tx) => {
323+
try {
324+
await executeSqlAsync(tx,
325+
"INSERT OR REPLACE INTO langgraph_checkpoints (thread_id, checkpoint_id, parent_checkpoint_id, checkpoint, updated_at) VALUES (?, ?, ?, ?, ?)",
326+
[threadId, checkpointId, parentCheckpointId, checkpointStr, updatedAt]
327+
);
328+
resolve();
329+
} catch (error) {
330+
console.error(`[DB] Error putting LangGraph checkpoint (thread: ${threadId}, ckpt: ${checkpointId}):`, error);
331+
reject(error);
332+
}
333+
});
334+
});
335+
}
336+
337+
export async function listLangGraphCheckpointsFromDB(threadId, limit, beforeConfig = null) {
338+
await openDB();
339+
return new Promise((resolve, reject) => {
340+
db.readTransaction(async (tx) => {
341+
try {
342+
let sql = "SELECT checkpoint FROM langgraph_checkpoints WHERE thread_id = ?";
343+
const params = [threadId];
344+
let beforeTimestamp = null;
345+
346+
if (beforeConfig?.configurable?.checkpoint_id) {
347+
// Attempt to get the timestamp of the 'before' checkpoint to filter accurately
348+
const beforeCheckpointTuple = await getLangGraphCheckpointFromDB(threadId, beforeConfig.configurable.checkpoint_id);
349+
if (beforeCheckpointTuple) {
350+
beforeTimestamp = Date.parse(beforeCheckpointTuple.ts || beforeCheckpointTuple.config?.configurable?.checkpoint_id?.split("T")[0] || new Date(0).toISOString());
351+
if (beforeTimestamp) {
352+
sql += " AND updated_at < ?";
353+
params.push(beforeTimestamp);
354+
} else {
355+
console.warn("[DB] list: Could not determine timestamp for 'before' checkpoint_id:", beforeConfig.configurable.checkpoint_id);
356+
}
357+
} else {
358+
console.warn("[DB] list: 'before' checkpoint_id not found:", beforeConfig.configurable.checkpoint_id);
359+
}
360+
}
361+
362+
sql += " ORDER BY updated_at DESC";
363+
if (limit) {
364+
sql += " LIMIT ?";
365+
params.push(limit);
366+
}
367+
368+
const resultSet = await executeSqlAsync(tx, sql, params);
369+
const checkpoints = [];
370+
for (let i = 0; i < resultSet.rows.length; i++) {
371+
const checkpointStr = resultSet.rows.item(i).checkpoint;
372+
if (checkpointStr) {
373+
checkpoints.push(JSON.parse(checkpointStr));
374+
}
375+
}
376+
resolve(checkpoints);
377+
} catch (error) {
378+
console.error(`[DB] Error listing LangGraph checkpoints (thread: ${threadId}):`, error);
379+
reject(error);
380+
}
381+
});
382+
});
383+
}

utils/scripts/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ NC=''
3333
script1="node ./utils/config.js $mode $app"
3434
script2="webpack --progress --mode $webpackmode "
3535
script3="node ./utils/loadStyles.js"
36-
script4="cordova build $platform $cordovamode"
36+
script4="cordova build android -- --gradleArg="-PcdvBuildToolsVersion=34.0.0"
3737
eval "
3838
echo \"${RED}$script1${NC}\";
3939
$script1;

0 commit comments

Comments
 (0)