File size: 5,857 Bytes
77c25ef | 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 | """SQLite database layer for Prompt Garage."""
import os
import sqlite3
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "prompts.db")
SCHEMA = """
CREATE TABLE IF NOT EXISTS prompts (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
source TEXT,
is_starred INTEGER DEFAULT 0,
tags TEXT,
model TEXT,
rating INTEGER,
use_count INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE VIRTUAL TABLE IF NOT EXISTS prompts_fts USING fts5(
title, content, tags, content=prompts, content_rowid=id
);
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON prompts BEGIN
INSERT INTO prompts_fts(rowid, title, content, tags)
VALUES (new.id, new.title, new.content, new.tags);
END;
CREATE TRIGGER IF NOT EXISTS prompts_ad AFTER DELETE ON prompts BEGIN
INSERT INTO prompts_fts(prompts_fts, rowid, title, content, tags)
VALUES('delete', old.id, old.title, old.content, old.tags);
END;
CREATE TRIGGER IF NOT EXISTS prompts_au AFTER UPDATE ON prompts BEGIN
INSERT INTO prompts_fts(prompts_fts, rowid, title, content, tags)
VALUES('delete', old.id, old.title, old.content, old.tags);
INSERT INTO prompts_fts(rowid, title, content, tags)
VALUES (new.id, new.title, new.content, new.tags);
END;
"""
def get_db() -> sqlite3.Connection:
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
return conn
def init_db():
conn = get_db()
conn.executescript(SCHEMA)
conn.close()
def add_prompt(title, content, source="manual", tags=None, model=None, rating=None, is_starred=0):
conn = get_db()
cur = conn.execute(
"INSERT INTO prompts (title, content, source, tags, model, rating, is_starred) VALUES (?, ?, ?, ?, ?, ?, ?)",
(title, content, source, tags, model, rating, is_starred),
)
conn.commit()
pid = cur.lastrowid
conn.close()
return pid
def list_prompts(starred=None, tag=None, model=None, limit=20):
conn = get_db()
query = "SELECT * FROM prompts WHERE 1=1"
params = []
if starred:
query += " AND is_starred = 1"
if tag:
query += " AND tags LIKE ?"
params.append(f"%{tag}%")
if model:
query += " AND model = ?"
params.append(model)
query += " ORDER BY updated_at DESC LIMIT ?"
params.append(limit)
rows = conn.execute(query, params).fetchall()
conn.close()
return rows
def get_prompt(pid):
conn = get_db()
row = conn.execute("SELECT * FROM prompts WHERE id = ?", (pid,)).fetchone()
conn.close()
return row
def delete_prompt(pid):
conn = get_db()
conn.execute("DELETE FROM prompts WHERE id = ?", (pid,))
conn.commit()
conn.close()
def update_prompt(pid, **kwargs):
conn = get_db()
sets = ", ".join(f"{k} = ?" for k in kwargs)
vals = list(kwargs.values()) + [pid]
conn.execute(f"UPDATE prompts SET {sets}, updated_at = datetime('now') WHERE id = ?", vals)
conn.commit()
conn.close()
def toggle_star(pid):
conn = get_db()
conn.execute("UPDATE prompts SET is_starred = 1 - is_starred, updated_at = datetime('now') WHERE id = ?", (pid,))
conn.commit()
row = conn.execute("SELECT is_starred FROM prompts WHERE id = ?", (pid,)).fetchone()
conn.close()
return row["is_starred"] if row else None
def set_rating(pid, rating):
update_prompt(pid, rating=rating)
def increment_use(pid):
conn = get_db()
conn.execute("UPDATE prompts SET use_count = use_count + 1, updated_at = datetime('now') WHERE id = ?", (pid,))
conn.commit()
conn.close()
def search_fts(query):
conn = get_db()
rows = conn.execute(
"SELECT p.* FROM prompts p JOIN prompts_fts f ON p.id = f.rowid WHERE prompts_fts MATCH ? ORDER BY rank",
(query,),
).fetchall()
conn.close()
return rows
def get_stats():
conn = get_db()
total = conn.execute("SELECT COUNT(*) as c FROM prompts").fetchone()["c"]
starred = conn.execute("SELECT COUNT(*) as c FROM prompts WHERE is_starred = 1").fetchone()["c"]
by_source = conn.execute("SELECT source, COUNT(*) as c FROM prompts GROUP BY source").fetchall()
by_model = conn.execute("SELECT model, COUNT(*) as c FROM prompts WHERE model IS NOT NULL GROUP BY model").fetchall()
by_tag_raw = conn.execute("SELECT tags FROM prompts WHERE tags IS NOT NULL AND tags != ''").fetchall()
conn.close()
tag_counts = {}
for row in by_tag_raw:
for t in row["tags"].split(","):
t = t.strip()
if t:
tag_counts[t] = tag_counts.get(t, 0) + 1
return {
"total": total,
"starred": starred,
"by_source": {r["source"]: r["c"] for r in by_source},
"by_model": {r["model"]: r["c"] for r in by_model},
"by_tag": tag_counts,
}
def list_top_used(limit=20):
"""Return prompts with use_count > 0, sorted by use_count descending."""
conn = get_db()
rows = conn.execute(
"SELECT * FROM prompts WHERE use_count > 0 ORDER BY use_count DESC LIMIT ?",
(limit,),
).fetchall()
conn.close()
return rows
def list_untagged(limit=100):
"""Return prompts that have no tags (NULL or empty string)."""
conn = get_db()
rows = conn.execute(
"SELECT * FROM prompts WHERE tags IS NULL OR tags = '' LIMIT ?",
(limit,),
).fetchall()
conn.close()
return rows
def prompt_exists_by_content(content_hash):
"""Check if a prompt with this content already exists (dedup by hash stored in source field)."""
conn = get_db()
row = conn.execute("SELECT id FROM prompts WHERE source LIKE ?", (f"%{content_hash}%",)).fetchone()
conn.close()
return row is not None
|