feat: add logic_level and primary_use metadata to model groups
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:
+21
-5
@@ -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 }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 : '—') + '</td>';
|
||||||
|
html += '<td>' + this.esc(g.primary_use || '—') + '</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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user