mysql_genius-core 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/mysql_genius/core/ai/client.rb +44 -8
- data/lib/mysql_genius/core/ai/config.rb +2 -1
- data/lib/mysql_genius/core/version.rb +1 -1
- data/lib/mysql_genius/core/views/mysql_genius/queries/dashboard.html.erb +28 -17
- data/lib/mysql_genius/core/views/mysql_genius/queries/query_detail.html.erb +4 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 321f2469ac2b5213d704f7afd4ac6211a0a327d1c8d054c9fdb95843dd4b2bde
|
|
4
|
+
data.tar.gz: 62a5b202b55bf2ffa2c7d76b5c263c7685bd4d9de14f206903ae35604dd146ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5666c4264a19bfddb7052233e3d02c9499fdbf7ea6ffb3b0e4fa60c1bea117de03a3a349cf399b716cb947733050ac053a5cc52c287d2798b17ed7f6160c6306
|
|
7
|
+
data.tar.gz: c5f99646c2c9da80629824366b5ee1a0dcf7710cf84bf06f2cfe8e783d4f42b1a3fd0d4635a9ddc1fd7ff29ea5c7e4e77734bd4f995fef47ea0a32a6174e4504
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.2
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Anthropic Messages API support** in `Core::Ai::Client` — detects `:x_api_key` auth style, sends `x-api-key` + `anthropic-version` headers, uses top-level `system` parameter and `content[0].text` response parsing.
|
|
7
|
+
- **`max_tokens` field** on `Core::Ai::Config` (default 4096) — sent in both OpenAI and Anthropic request bodies.
|
|
8
|
+
- **Copy response button** on all AI result sections in the shared dashboard template.
|
|
9
|
+
- **Dark mode CSS classes** for AI result sections (`mg-ai-section`, `mg-ai-danger`, `mg-ai-warning`, `mg-ai-info`) replacing hardcoded inline styles.
|
|
10
|
+
- **`capability?(:standalone_header)`** guard on the dashboard heading — hides it when the rendering adapter provides its own header.
|
|
11
|
+
|
|
3
12
|
## 0.7.1
|
|
4
13
|
|
|
5
14
|
### Fixed
|
|
@@ -27,12 +27,11 @@ module MysqlGenius
|
|
|
27
27
|
|
|
28
28
|
raise NotConfigured, "AI is not configured" unless @config.enabled?
|
|
29
29
|
|
|
30
|
-
body =
|
|
31
|
-
messages
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
body[:model] = @config.model if @config.model && !@config.model.empty?
|
|
30
|
+
body = if anthropic?
|
|
31
|
+
build_anthropic_body(messages, temperature)
|
|
32
|
+
else
|
|
33
|
+
build_openai_body(messages, temperature)
|
|
34
|
+
end
|
|
36
35
|
|
|
37
36
|
response = post_with_redirects(URI(@config.endpoint), body.to_json)
|
|
38
37
|
parsed = JSON.parse(response.body)
|
|
@@ -41,7 +40,11 @@ module MysqlGenius
|
|
|
41
40
|
raise ApiError, "AI API error: #{parsed["error"]["message"] || parsed["error"]}"
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
content =
|
|
43
|
+
content = if anthropic?
|
|
44
|
+
parsed.dig("content", 0, "text")
|
|
45
|
+
else
|
|
46
|
+
parsed.dig("choices", 0, "message", "content")
|
|
47
|
+
end
|
|
45
48
|
raise ApiError, "No content in AI response" if content.nil?
|
|
46
49
|
|
|
47
50
|
parse_json_content(content)
|
|
@@ -49,6 +52,35 @@ module MysqlGenius
|
|
|
49
52
|
|
|
50
53
|
private
|
|
51
54
|
|
|
55
|
+
def anthropic?
|
|
56
|
+
@config.auth_style == :x_api_key
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_openai_body(messages, temperature)
|
|
60
|
+
body = {
|
|
61
|
+
messages: messages,
|
|
62
|
+
response_format: { type: "json_object" },
|
|
63
|
+
temperature: temperature,
|
|
64
|
+
}
|
|
65
|
+
body[:max_tokens] = @config.max_tokens if @config.max_tokens
|
|
66
|
+
body[:model] = @config.model if @config.model && !@config.model.empty?
|
|
67
|
+
body
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def build_anthropic_body(messages, temperature)
|
|
71
|
+
system_text = messages.select { |m| m[:role] == "system" }.map { |m| m[:content] }.join("\n\n")
|
|
72
|
+
user_messages = messages.reject { |m| m[:role] == "system" }
|
|
73
|
+
|
|
74
|
+
body = {
|
|
75
|
+
messages: user_messages,
|
|
76
|
+
max_tokens: @config.max_tokens || 4096,
|
|
77
|
+
temperature: temperature,
|
|
78
|
+
}
|
|
79
|
+
body[:system] = system_text unless system_text.empty?
|
|
80
|
+
body[:model] = @config.model if @config.model && !@config.model.empty?
|
|
81
|
+
body
|
|
82
|
+
end
|
|
83
|
+
|
|
52
84
|
def parse_json_content(content)
|
|
53
85
|
JSON.parse(content)
|
|
54
86
|
rescue JSON::ParserError
|
|
@@ -78,8 +110,12 @@ module MysqlGenius
|
|
|
78
110
|
|
|
79
111
|
request = Net::HTTP::Post.new(uri)
|
|
80
112
|
request["Content-Type"] = "application/json"
|
|
81
|
-
|
|
113
|
+
case @config.auth_style
|
|
114
|
+
when :bearer
|
|
82
115
|
request["Authorization"] = "Bearer #{@config.api_key}"
|
|
116
|
+
when :x_api_key
|
|
117
|
+
request["x-api-key"] = @config.api_key
|
|
118
|
+
request["anthropic-version"] = "2023-06-01"
|
|
83
119
|
else
|
|
84
120
|
request["api-key"] = @config.api_key
|
|
85
121
|
end
|
|
@@ -26,10 +26,11 @@ module MysqlGenius
|
|
|
26
26
|
:auth_style,
|
|
27
27
|
:system_context,
|
|
28
28
|
:domain_context,
|
|
29
|
+
:max_tokens,
|
|
29
30
|
keyword_init: true,
|
|
30
31
|
) do
|
|
31
32
|
def initialize(**kwargs)
|
|
32
|
-
super(domain_context: "", **kwargs)
|
|
33
|
+
super(domain_context: "", max_tokens: 4096, **kwargs)
|
|
33
34
|
freeze
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
<% if capability?(:standalone_header) %>
|
|
1
2
|
<div style="display:flex;align-items:center;justify-content:space-between;">
|
|
2
3
|
<h4>🐘 MySQLGenius</h4>
|
|
3
4
|
<button class="mg-theme-toggle" id="mg-theme-btn" title="Toggle dark/light theme" onclick="(function(){var d=document.documentElement,t=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',t);localStorage.setItem('mg-theme',t);document.getElementById('mg-theme-btn').textContent=t==='dark'?'\u2600\uFE0F':'\uD83C\uDF19';})()">
|
|
4
5
|
<script>document.write(document.documentElement.getAttribute('data-theme')==='dark'?'\u2600\uFE0F':'\uD83C\uDF19')</script>
|
|
5
6
|
</button>
|
|
6
7
|
</div>
|
|
8
|
+
<% end %>
|
|
7
9
|
|
|
8
10
|
<div class="mg-tabs">
|
|
9
11
|
<button class="mg-tab active" data-tab="dashboard">Dashboard</button>
|
|
@@ -825,7 +827,7 @@
|
|
|
825
827
|
});
|
|
826
828
|
|
|
827
829
|
ajax('POST', ROUTES.optimize, data, function(resp) {
|
|
828
|
-
el('optimize-content').innerHTML = formatMarkdown(resp.suggestions || 'No suggestions available.');
|
|
830
|
+
el('optimize-content').innerHTML = formatMarkdown(resp.suggestions || 'No suggestions available.') + copyButton('optimize-content');
|
|
829
831
|
show(el('optimize-results'));
|
|
830
832
|
explainOptimize.disabled = false;
|
|
831
833
|
explainOptimize.innerHTML = '⚡ AI Optimization';
|
|
@@ -1230,9 +1232,25 @@
|
|
|
1230
1232
|
});
|
|
1231
1233
|
}
|
|
1232
1234
|
|
|
1235
|
+
function copyButton(targetId) {
|
|
1236
|
+
return '<div style="text-align:right;margin-top:8px;"><button class="mg-btn mg-btn-outline-secondary mg-btn-sm mg-copy-ai" data-target="' + targetId + '">Copy response</button></div>';
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
document.addEventListener('click', function(e) {
|
|
1240
|
+
var btn = e.target.closest('.mg-copy-ai');
|
|
1241
|
+
if (!btn) return;
|
|
1242
|
+
var target = el(btn.dataset.target);
|
|
1243
|
+
if (!target) return;
|
|
1244
|
+
var text = target.innerText || target.textContent;
|
|
1245
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
1246
|
+
btn.textContent = 'Copied!';
|
|
1247
|
+
setTimeout(function() { btn.textContent = 'Copy response'; }, 2000);
|
|
1248
|
+
});
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1233
1251
|
function showAiQueryResult(title, html) {
|
|
1234
1252
|
el('ai-query-title').innerHTML = '<strong>⚡ ' + escHtml(title) + '</strong>';
|
|
1235
|
-
el('ai-query-content').innerHTML = html;
|
|
1253
|
+
el('ai-query-content').innerHTML = html + copyButton('ai-query-content');
|
|
1236
1254
|
show(el('ai-query-result'));
|
|
1237
1255
|
}
|
|
1238
1256
|
|
|
@@ -1306,7 +1324,7 @@
|
|
|
1306
1324
|
});
|
|
1307
1325
|
|
|
1308
1326
|
aiCall(ROUTES.index_advisor, data, function(resp) {
|
|
1309
|
-
el('optimize-content').innerHTML = formatMarkdown(resp.indexes || resp.raw || 'No suggestions.');
|
|
1327
|
+
el('optimize-content').innerHTML = formatMarkdown(resp.indexes || resp.raw || 'No suggestions.') + copyButton('optimize-content');
|
|
1310
1328
|
show(el('optimize-results'));
|
|
1311
1329
|
indexAdvisor.disabled = false;
|
|
1312
1330
|
indexAdvisor.innerHTML = '⚡ Index Advisor';
|
|
@@ -1329,7 +1347,7 @@
|
|
|
1329
1347
|
hide(el('server-ai-result'));
|
|
1330
1348
|
aiCall(ROUTES.root_cause, {}, function(data) {
|
|
1331
1349
|
el('server-ai-title').innerHTML = '<strong>⚡ Root Cause Analysis</strong>';
|
|
1332
|
-
el('server-ai-content').innerHTML = formatMarkdown(data.diagnosis || data.raw || 'No diagnosis.');
|
|
1350
|
+
el('server-ai-content').innerHTML = formatMarkdown(data.diagnosis || data.raw || 'No diagnosis.') + copyButton('server-ai-content');
|
|
1333
1351
|
show(el('server-ai-result'));
|
|
1334
1352
|
rootCauseBtn.disabled = false;
|
|
1335
1353
|
rootCauseBtn.innerHTML = '⚡ Why is it slow?';
|
|
@@ -1354,7 +1372,7 @@
|
|
|
1354
1372
|
hide(el('server-ai-result'));
|
|
1355
1373
|
aiCall(ROUTES.anomaly_detection, {}, function(data) {
|
|
1356
1374
|
el('server-ai-title').innerHTML = '<strong>⚡ Query Health Report</strong>';
|
|
1357
|
-
el('server-ai-content').innerHTML = formatMarkdown(data.report || data.raw || 'No anomalies detected.');
|
|
1375
|
+
el('server-ai-content').innerHTML = formatMarkdown(data.report || data.raw || 'No anomalies detected.') + copyButton('server-ai-content');
|
|
1358
1376
|
show(el('server-ai-result'));
|
|
1359
1377
|
anomalyBtn.disabled = false;
|
|
1360
1378
|
anomalyBtn.innerHTML = '⚡ Anomaly Detection';
|
|
@@ -1378,7 +1396,7 @@
|
|
|
1378
1396
|
hide(el('schema-result'));
|
|
1379
1397
|
var table = el('schema-table').value;
|
|
1380
1398
|
aiCall(ROUTES.schema_review, { table: table }, function(data) {
|
|
1381
|
-
el('schema-result-content').innerHTML = formatFindings(data.findings || data.raw || '');
|
|
1399
|
+
el('schema-result-content').innerHTML = formatFindings(data.findings || data.raw || '') + copyButton('schema-result-content');
|
|
1382
1400
|
show(el('schema-result'));
|
|
1383
1401
|
schemaBtn.disabled = false;
|
|
1384
1402
|
schemaBtn.innerHTML = '⚡ Analyze Schema';
|
|
@@ -1409,7 +1427,7 @@
|
|
|
1409
1427
|
var level = (data.risk_level || '').toLowerCase();
|
|
1410
1428
|
var badgeClass = level === 'critical' ? 'mg-badge-danger' : level === 'high' ? 'mg-badge-danger' : level === 'medium' ? 'mg-badge-warning' : 'mg-badge-info';
|
|
1411
1429
|
el('migration-risk-badge').innerHTML = level ? '<span class="mg-badge ' + badgeClass + '" style="font-size:14px;padding:4px 12px;">Risk: ' + level.toUpperCase() + '</span>' : '';
|
|
1412
|
-
el('migration-result-content').innerHTML = formatFindings(data.assessment || data.raw || '');
|
|
1430
|
+
el('migration-result-content').innerHTML = formatFindings(data.assessment || data.raw || '') + copyButton('migration-result-content');
|
|
1413
1431
|
show(el('migration-result'));
|
|
1414
1432
|
migrationBtn.disabled = false;
|
|
1415
1433
|
migrationBtn.innerHTML = '⚡ Assess Risk';
|
|
@@ -1565,19 +1583,12 @@
|
|
|
1565
1583
|
|
|
1566
1584
|
if (!sections.length) return formatMarkdown(text);
|
|
1567
1585
|
|
|
1568
|
-
var badgeColors = { danger: '#dc3545', warning: '#ffc107', info: '#17a2b8' };
|
|
1569
|
-
var bgColors = { danger: '#fff5f5', warning: '#fffbeb', info: '#f0f9ff' };
|
|
1570
|
-
var borderColors = { danger: '#f5c6cb', warning: '#ffeeba', info: '#bee5eb' };
|
|
1571
|
-
|
|
1572
1586
|
return sections.map(function(sec) {
|
|
1573
1587
|
var content = formatMarkdown(sec.lines.join('\n').trim());
|
|
1574
1588
|
if (!content || content === '<br>') return '';
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
return '<div class="mg-card mg-mb" style="border-left:4px solid ' + badge + ';background:' + bg + ';border-color:' + border + ';">' +
|
|
1579
|
-
(sec.title ? '<div class="mg-card-header" style="background:transparent;border-bottom:1px solid ' + border + ';"><strong>' + escHtml(sec.title) + '</strong></div>' : '') +
|
|
1580
|
-
'<div class="mg-card-body" style="font-size:13px;">' + content + '</div></div>';
|
|
1589
|
+
return '<div class="mg-ai-section mg-ai-' + sec.severity + ' mg-mb">' +
|
|
1590
|
+
(sec.title ? '<div class="mg-ai-section-header"><strong>' + escHtml(sec.title) + '</strong></div>' : '') +
|
|
1591
|
+
'<div class="mg-ai-section-body">' + content + '</div></div>';
|
|
1581
1592
|
}).filter(function(s) { return s; }).join('');
|
|
1582
1593
|
}
|
|
1583
1594
|
})();
|
|
@@ -411,13 +411,13 @@
|
|
|
411
411
|
// --- Explain ---
|
|
412
412
|
|
|
413
413
|
// performance_schema DIGEST_TEXT uses normalized spacing that isn't valid SQL:
|
|
414
|
-
// "SELECT COUNT ( * ) FROM `riders`"
|
|
415
|
-
// "... IN ( ... )"
|
|
414
|
+
// "SELECT COUNT ( * ) FROM `riders`" -> needs to become "SELECT COUNT(*) FROM `riders`"
|
|
415
|
+
// "... IN ( ... )" -> "... IN (...)"
|
|
416
416
|
// Also replaces placeholder ? with 1 so EXPLAIN can parse it.
|
|
417
417
|
function normalizeDigestSql(sql) {
|
|
418
418
|
return sql
|
|
419
|
-
.replace(/\(\s+/g, '(') // "( "
|
|
420
|
-
.replace(/\s+\)/g, ')') // " )"
|
|
419
|
+
.replace(/\(\s+/g, '(') // "( " -> "("
|
|
420
|
+
.replace(/\s+\)/g, ')') // " )" -> ")"
|
|
421
421
|
.replace(/\s*,\s*/g, ', ') // normalize comma spacing
|
|
422
422
|
.replace(/\?/g, '1'); // replace ? placeholders with literal 1
|
|
423
423
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mysql_genius-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antarr Byrd
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Shared library used by the mysql_genius Rails engine and the mysql_genius-desktop
|
|
14
14
|
standalone app. Contains the SQL validator, query runner, database analyses, and
|