135 lines
3.5 KiB
TypeScript
135 lines
3.5 KiB
TypeScript
import Database from "better-sqlite3";
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
|
|
const SCHEMA_SQL = `
|
|
CREATE TABLE IF NOT EXISTS workspaces (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
channel_id TEXT NOT NULL UNIQUE,
|
|
guild_id TEXT NOT NULL,
|
|
agent_name TEXT NOT NULL UNIQUE,
|
|
workspace_path TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
|
|
discord_msg_id TEXT NOT NULL UNIQUE,
|
|
role TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
author_name TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_conversations_workspace
|
|
ON conversations(workspace_id, created_at);
|
|
`;
|
|
|
|
export interface Workspace {
|
|
id: number;
|
|
channel_id: string;
|
|
guild_id: string;
|
|
agent_name: string;
|
|
workspace_path: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Conversation {
|
|
id: number;
|
|
workspace_id: number;
|
|
discord_msg_id: string;
|
|
role: "user" | "assistant";
|
|
content: string;
|
|
author_name: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export class DisclawDatabase {
|
|
private db: Database.Database;
|
|
|
|
constructor(dbPath: string) {
|
|
const dir = path.dirname(dbPath);
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
|
|
this.db = new Database(dbPath);
|
|
this.db.pragma("journal_mode = WAL");
|
|
this.db.pragma("foreign_keys = ON");
|
|
this.runMigrations();
|
|
}
|
|
|
|
private runMigrations(): void {
|
|
this.db.exec(SCHEMA_SQL);
|
|
}
|
|
|
|
// --- Workspace queries ---
|
|
|
|
getWorkspaceByChannelId(channelId: string): Workspace | undefined {
|
|
return this.db
|
|
.prepare("SELECT * FROM workspaces WHERE channel_id = ?")
|
|
.get(channelId) as Workspace | undefined;
|
|
}
|
|
|
|
getWorkspaceByName(name: string): Workspace | undefined {
|
|
return this.db
|
|
.prepare("SELECT * FROM workspaces WHERE agent_name = ?")
|
|
.get(name) as Workspace | undefined;
|
|
}
|
|
|
|
getAllWorkspaces(): Workspace[] {
|
|
return this.db
|
|
.prepare("SELECT * FROM workspaces ORDER BY created_at")
|
|
.all() as Workspace[];
|
|
}
|
|
|
|
createWorkspace(
|
|
channelId: string,
|
|
guildId: string,
|
|
agentName: string,
|
|
workspacePath: string
|
|
): Workspace {
|
|
const stmt = this.db.prepare(
|
|
"INSERT INTO workspaces (channel_id, guild_id, agent_name, workspace_path) VALUES (?, ?, ?, ?)"
|
|
);
|
|
const result = stmt.run(channelId, guildId, agentName, workspacePath);
|
|
return this.db
|
|
.prepare("SELECT * FROM workspaces WHERE id = ?")
|
|
.get(result.lastInsertRowid) as Workspace;
|
|
}
|
|
|
|
// --- Conversation queries ---
|
|
|
|
addConversation(
|
|
workspaceId: number,
|
|
discordMsgId: string,
|
|
role: "user" | "assistant",
|
|
content: string,
|
|
authorName: string | null
|
|
): void {
|
|
this.db
|
|
.prepare(
|
|
"INSERT OR IGNORE INTO conversations (workspace_id, discord_msg_id, role, content, author_name) VALUES (?, ?, ?, ?, ?)"
|
|
)
|
|
.run(workspaceId, discordMsgId, role, content, authorName);
|
|
}
|
|
|
|
getConversationHistory(
|
|
workspaceId: number,
|
|
limit: number = 50
|
|
): Conversation[] {
|
|
return this.db
|
|
.prepare(
|
|
`SELECT * FROM conversations
|
|
WHERE workspace_id = ?
|
|
ORDER BY created_at DESC
|
|
LIMIT ?`
|
|
)
|
|
.all(workspaceId, limit) as Conversation[];
|
|
}
|
|
|
|
close(): void {
|
|
this.db.close();
|
|
}
|
|
}
|