feat: save account model and thinking settings for Hermes
Rebuild & Deploy / rebuild (push) Successful in 1m9s
Rebuild & Deploy / rebuild (push) Successful in 1m9s
This commit is contained in:
1 parent
a45569f8c1
commit
6cb4427210
13 files changed
+798
-16
No files matched your search
@@ -14,6 +14,8 @@ from pathlib import Path
|
||||
|
||||
PUBLIC_FIELDS = ('id', 'username', 'display_name', 'role', 'status', 'container_name', 'error')
|
||||
SLUG = re.compile(r'[a-z][a-z0-9]*(?:-[a-z0-9]+)*\Z')
|
||||
MODEL = re.compile(r'[!-~]{1,200}\Z')
|
||||
THINKING_LEVELS = ('none', 'minimal', 'low', 'medium', 'high', 'xhigh')
|
||||
|
||||
|
||||
def public_user(user):
|
||||
@@ -74,6 +76,10 @@ class Accounts:
|
||||
error TEXT, password_hash TEXT NOT NULL,
|
||||
hermes_url TEXT, hermes_api_key TEXT, desired TEXT NOT NULL DEFAULT 'ready'
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS account_model_settings (
|
||||
user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||||
model TEXT NOT NULL, thinking_level TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
hash TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at REAL NOT NULL
|
||||
@@ -105,6 +111,38 @@ class Accounts:
|
||||
row = self.db.execute('SELECT * FROM users WHERE id=?', (user_id,)).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def get_model_settings(self, user_id):
|
||||
with self.lock:
|
||||
# The join checks account availability and reads settings in one
|
||||
# snapshot, including accounts that have not finished provisioning.
|
||||
row = self.db.execute('''
|
||||
SELECT COALESCE(s.model, 'auto') AS model,
|
||||
COALESCE(s.thinking_level, 'none') AS thinking_level
|
||||
FROM users u LEFT JOIN account_model_settings s ON s.user_id=u.id
|
||||
WHERE u.id=? AND u.desired!='deleted'
|
||||
''', (user_id,)).fetchone()
|
||||
if row is None:
|
||||
raise KeyError(user_id)
|
||||
return dict(row)
|
||||
|
||||
def set_model_settings(self, user_id, model, thinking_level):
|
||||
if not isinstance(model, str) or not MODEL.fullmatch(model):
|
||||
raise ValueError('Model must contain 1–200 printable ASCII characters without whitespace')
|
||||
if not isinstance(thinking_level, str) or thinking_level not in THINKING_LEVELS:
|
||||
raise ValueError('Invalid thinking level')
|
||||
with self.lock, self.db:
|
||||
# Guard and upsert are one write statement: a delete on another
|
||||
# connection cannot interleave between checking and storing.
|
||||
cursor = self.db.execute('''
|
||||
INSERT INTO account_model_settings(user_id,model,thinking_level)
|
||||
SELECT id,?,? FROM users WHERE id=? AND desired!='deleted'
|
||||
ON CONFLICT(user_id) DO UPDATE SET
|
||||
model=excluded.model, thinking_level=excluded.thinking_level
|
||||
''', (model, thinking_level, user_id))
|
||||
if cursor.rowcount != 1:
|
||||
raise KeyError(user_id)
|
||||
return {'model': model, 'thinking_level': thinking_level}
|
||||
|
||||
def by_username(self, username):
|
||||
with self.lock:
|
||||
row = self.db.execute('SELECT * FROM users WHERE username=?', (username,)).fetchone()
|
||||
|
||||
Reference in new issue
Block a user