feat: add logic_level and primary_use metadata to model groups
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

Schema: Added logic_level (INTEGER) and primary_use (TEXT) columns
to model_groups table with auto-migration for existing databases.

Seed: Three new default groups:
  heavy-logic  (level 9) — Complex Coding, Logic, Agents
  standard-pro (level 5) — General Assistant, Long Docs
  fast-flow    (level 2) — Classification, JSON, Basic Q&A

Admin API: INSERT/UPDATE handlers now accept and persist the new fields.
Dashboard: Table shows Level and Primary Use columns; form includes
both fields with appropriate inputs and placeholders.
This commit is contained in:
2026-05-07 12:01:28 -04:00
parent 79dd122b56
commit a3a6f765e7
3 changed files with 45 additions and 11 deletions
+21 -5
View File
@@ -130,6 +130,8 @@ func (db *DB) RunMigrations() error {
targets TEXT NOT NULL DEFAULT '[]', targets TEXT NOT NULL DEFAULT '[]',
complexity_threshold INTEGER, complexity_threshold INTEGER,
heuristic_rules TEXT, heuristic_rules TEXT,
logic_level INTEGER,
primary_use TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`, )`,
@@ -162,6 +164,10 @@ func (db *DB) RunMigrations() error {
} }
} }
// Add columns to existing model_groups tables (safe — SQLite ignores duplicates on error)
db.Exec("ALTER TABLE model_groups ADD COLUMN logic_level INTEGER")
db.Exec("ALTER TABLE model_groups ADD COLUMN primary_use TEXT")
// Default admin user // Default admin user
var count int var count int
if err := db.Get(&count, "SELECT COUNT(*) FROM users"); err != nil { if err := db.Get(&count, "SELECT COUNT(*) FROM users"); err != nil {
@@ -190,14 +196,19 @@ func (db *DB) RunMigrations() error {
// Seed default model groups // Seed default model groups
defaultGroups := []struct { defaultGroups := []struct {
id, strategy, targets string id, strategy, targets string
logicLevel *int
primaryUse *string
}{ }{
{"deepseek-auto", "heuristic", `["deepseek-chat","deepseek-reasoner"]`}, {"deepseek-auto", "heuristic", `["deepseek-chat","deepseek-reasoner"]`, nil, nil},
{"openai-auto", "heuristic", `["gpt-4o-mini","gpt-4o"]`}, {"openai-auto", "heuristic", `["gpt-4o-mini","gpt-4o"]`, nil, nil},
{"gemini-auto", "heuristic", `["gemini-2.0-flash","gemini-2.5-pro"]`}, {"gemini-auto", "heuristic", `["gemini-2.0-flash","gemini-2.5-pro"]`, nil, nil},
{"heavy-logic", "heuristic", `["grok-4.3","kimi-k2.5","deepseek-v4-pro"]`, intPtr(9), strPtr("Complex Coding, Logic, Agents.")},
{"standard-pro", "heuristic", `["gpt-5.4-mini","gemini-3-flash"]`, intPtr(5), strPtr("General Assistant, Long Docs.")},
{"fast-flow", "heuristic", `["deepseek-v4-flash","gpt-5.4-nano"]`, intPtr(2), strPtr("Classification, JSON, Basic Q&A.")},
} }
for _, g := range defaultGroups { for _, g := range defaultGroups {
db.Exec(`INSERT OR IGNORE INTO model_groups (id, strategy, targets) VALUES (?, ?, ?)`, db.Exec(`INSERT OR IGNORE INTO model_groups (id, strategy, targets, logic_level, primary_use) VALUES (?, ?, ?, ?, ?)`,
g.id, g.strategy, g.targets) g.id, g.strategy, g.targets, g.logicLevel, g.primaryUse)
} }
return nil return nil
@@ -293,6 +304,11 @@ type ModelGroup struct {
Targets string `db:"targets" json:"targets"` // JSON array Targets string `db:"targets" json:"targets"` // JSON array
ComplexityThreshold *int `db:"complexity_threshold" json:"complexity_threshold"` ComplexityThreshold *int `db:"complexity_threshold" json:"complexity_threshold"`
HeuristicRules *string `db:"heuristic_rules" json:"heuristic_rules"` HeuristicRules *string `db:"heuristic_rules" json:"heuristic_rules"`
LogicLevel *int `db:"logic_level" json:"logic_level"`
PrimaryUse *string `db:"primary_use" json:"primary_use"`
CreatedAt time.Time `db:"created_at" json:"created_at"` CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
} }
func intPtr(v int) *int { return &v }
func strPtr(v string) *string { return &v }
+5 -5
View File
@@ -28,10 +28,10 @@ func (s *Server) handleCreateModelGroup(c *gin.Context) {
} }
_, err := s.database.Exec(` _, err := s.database.Exec(`
INSERT INTO model_groups (id, strategy, selector_model, targets, complexity_threshold, heuristic_rules) INSERT INTO model_groups (id, strategy, selector_model, targets, complexity_threshold, heuristic_rules, logic_level, primary_use)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
group.ID, group.Strategy, group.SelectorModel, group.Targets, group.ID, group.Strategy, group.SelectorModel, group.Targets,
group.ComplexityThreshold, group.HeuristicRules) group.ComplexityThreshold, group.HeuristicRules, group.LogicLevel, group.PrimaryUse)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
@@ -50,10 +50,10 @@ func (s *Server) handleUpdateModelGroup(c *gin.Context) {
} }
_, err := s.database.Exec(` _, err := s.database.Exec(`
UPDATE model_groups SET strategy=?, selector_model=?, targets=?, complexity_threshold=?, heuristic_rules=?, updated_at=CURRENT_TIMESTAMP UPDATE model_groups SET strategy=?, selector_model=?, targets=?, complexity_threshold=?, heuristic_rules=?, logic_level=?, primary_use=?, updated_at=CURRENT_TIMESTAMP
WHERE id=?`, WHERE id=?`,
group.Strategy, group.SelectorModel, group.Targets, group.Strategy, group.SelectorModel, group.Targets,
group.ComplexityThreshold, group.HeuristicRules, id) group.ComplexityThreshold, group.HeuristicRules, group.LogicLevel, group.PrimaryUse, id)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
+19 -1
View File
@@ -30,12 +30,14 @@ class ModelGroupsPage {
} }
let html = '<table class="data-table"><thead><tr>'; let html = '<table class="data-table"><thead><tr>';
html += '<th>Group ID</th><th>Strategy</th><th>Targets</th><th>Actions</th>'; html += '<th>Group ID</th><th>Level</th><th>Primary Use</th><th>Strategy</th><th>Targets</th><th>Actions</th>';
html += '</tr></thead><tbody>'; html += '</tr></thead><tbody>';
groups.forEach(g => { groups.forEach(g => {
html += '<tr>'; html += '<tr>';
html += '<td><code>' + this.esc(g.id) + '</code></td>'; html += '<td><code>' + this.esc(g.id) + '</code></td>';
html += '<td>' + (g.logic_level != null ? g.logic_level : '&mdash;') + '</td>';
html += '<td>' + this.esc(g.primary_use || '&mdash;') + '</td>';
html += '<td><span class="badge">' + this.esc(g.strategy) + '</span></td>'; html += '<td><span class="badge">' + this.esc(g.strategy) + '</span></td>';
html += '<td><code>' + this.esc(g.targets) + '</code></td>'; html += '<td><code>' + this.esc(g.targets) + '</code></td>';
html += '<td>'; html += '<td>';
@@ -106,6 +108,18 @@ class ModelGroupsPage {
<textarea id="mg-rules" rows="4" placeholder='[{"pattern":"step by step","target":1}]'>${group && group.heuristic_rules ? group.heuristic_rules : ''}</textarea> <textarea id="mg-rules" rows="4" placeholder='[{"pattern":"step by step","target":1}]'>${group && group.heuristic_rules ? group.heuristic_rules : ''}</textarea>
<small>Pattern to match in user messages. target = index into targets array.</small> <small>Pattern to match in user messages. target = index into targets array.</small>
</div> </div>
<div class="form-control">
<label>Logic Level (1-10)</label>
<input type="number" id="mg-level" value="${group && group.logic_level != null ? group.logic_level : ''}" min="1" max="10"
placeholder="e.g. 8 for heavy logic, 2 for fast/basic">
<small>Rough complexity scale. 1-3: fast/light, 4-7: standard, 8-10: heavy.</small>
</div>
<div class="form-control">
<label>Primary Use</label>
<input type="text" id="mg-primary-use" value="${this.esc(group && group.primary_use ? group.primary_use : '')}"
placeholder="e.g. Complex Coding, Logic, Agents.">
<small>Brief description of what this group is best used for.</small>
</div>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button> <button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn" onclick="document.getElementById('model-group-form').style.display='none'">Cancel</button> <button type="button" class="btn" onclick="document.getElementById('model-group-form').style.display='none'">Cancel</button>
@@ -129,12 +143,16 @@ class ModelGroupsPage {
var selectorModel = document.getElementById('mg-selector-model').value.trim() || null; var selectorModel = document.getElementById('mg-selector-model').value.trim() || null;
var thresholdVal = document.getElementById('mg-threshold').value; var thresholdVal = document.getElementById('mg-threshold').value;
var rules = document.getElementById('mg-rules').value.trim() || null; var rules = document.getElementById('mg-rules').value.trim() || null;
var logicLevelVal = document.getElementById('mg-level').value;
var primaryUse = document.getElementById('mg-primary-use').value.trim() || null;
try { JSON.parse(targets); } catch (e) { alert('Targets must be valid JSON array'); return; } try { JSON.parse(targets); } catch (e) { alert('Targets must be valid JSON array'); return; }
if (rules) { try { JSON.parse(rules); } catch (e) { alert('Heuristic rules must be valid JSON'); return; } } if (rules) { try { JSON.parse(rules); } catch (e) { alert('Heuristic rules must be valid JSON'); return; } }
var body = { id: id, strategy: strategy, targets: targets, selector_model: selectorModel, heuristic_rules: rules }; var body = { id: id, strategy: strategy, targets: targets, selector_model: selectorModel, heuristic_rules: rules };
if (thresholdVal) body.complexity_threshold = parseInt(thresholdVal); if (thresholdVal) body.complexity_threshold = parseInt(thresholdVal);
if (logicLevelVal) body.logic_level = parseInt(logicLevelVal);
if (primaryUse) body.primary_use = primaryUse;
try { try {
if (isEdit) { if (isEdit) {