Skip to content

Commit 68b306f

Browse files
authored
fix: improve Prompts tab UX with categories, human-readable names, and better intro (#218)
- Add Category field to PromptInfo for frontend grouping - Set category on all prompts: "workflow", "toolkit", "custom" - Exclude platform-overview from prompt metadata (auto-invoked, not copyable) - Group prompts into Workflows and Quick Actions sections - Display human-readable titles with machine slug shown below - Rewrite intro text to describe platform capabilities - Update test preview data and all affected tests
1 parent c386ef1 commit 68b306f

File tree

9 files changed

+166
-46
lines changed

9 files changed

+166
-46
lines changed

apps/platform-info/index.html

Lines changed: 92 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -616,11 +616,18 @@
616616
margin-bottom: 6px;
617617
}
618618

619-
.prompt-card-name {
620-
font-family: var(--font-mono);
621-
font-size: 13px;
619+
.prompt-card-title {
620+
font-size: 13.5px;
622621
font-weight: 600;
623622
color: var(--text);
623+
line-height: 1.3;
624+
}
625+
626+
.prompt-card-slug {
627+
font-family: var(--font-mono);
628+
font-size: 11px;
629+
color: var(--muted);
630+
margin-top: 2px;
624631
}
625632

626633
.prompt-card-desc {
@@ -630,6 +637,19 @@
630637
margin-bottom: 8px;
631638
}
632639

640+
.prompt-section-header {
641+
font-size: 11px;
642+
font-weight: 600;
643+
color: var(--muted);
644+
text-transform: uppercase;
645+
letter-spacing: 0.05em;
646+
margin-bottom: 8px;
647+
margin-top: 4px;
648+
}
649+
.prompt-section-header:not(:first-child) {
650+
margin-top: 16px;
651+
}
652+
633653
.prompt-card-args {
634654
display: flex;
635655
flex-direction: column;
@@ -750,7 +770,7 @@
750770
</div>
751771
</div>
752772
<div id="tab-prompts" class="tab-pane">
753-
<div class="tab-intro">Available prompts — select one to copy the prompt text with arguments filled in.</div>
773+
<div class="tab-intro" id="prompt-intro"></div>
754774
<div id="prompt-list" class="prompt-cards"></div>
755775
</div>
756776
<div id="tab-agent-instructions" class="tab-pane">
@@ -1079,7 +1099,7 @@
10791099

10801100
// Prompts tab
10811101
if (d.prompts && d.prompts.length) {
1082-
renderPrompts(d.prompts);
1102+
renderPrompts(d.prompts, brandName);
10831103
document.getElementById('tab-btn-prompts').classList.remove('hidden');
10841104
}
10851105

@@ -1093,42 +1113,83 @@
10931113
// Prompt data stored for copy
10941114
var _prompts = [];
10951115

1096-
function renderPrompts(prompts) {
1116+
function humanize(slug) {
1117+
return slug.replace(/[-_]/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); });
1118+
}
1119+
1120+
function renderPrompts(prompts, platformName) {
10971121
_prompts = prompts;
1122+
1123+
// Set intro text
1124+
platformName = platformName || 'This platform';
1125+
document.getElementById('prompt-intro').textContent =
1126+
platformName + ' has many capabilities built in, including the ability to find insights in your data, visualize it, generate reports, and more. Use the prompts below to kick off common workflows or copy a prompt name to invoke it in your MCP client.';
1127+
1128+
// Group by category
1129+
var workflows = [];
1130+
var quickActions = [];
1131+
prompts.forEach(function(p, idx) {
1132+
p._idx = idx;
1133+
if (p.category === 'workflow' || (p.arguments && p.arguments.length)) {
1134+
workflows.push(p);
1135+
} else {
1136+
quickActions.push(p);
1137+
}
1138+
});
1139+
10981140
var el = document.getElementById('prompt-list');
1099-
el.innerHTML = prompts.map(function(p, idx) {
1100-
var argsHtml = '';
1101-
if (p.arguments && p.arguments.length) {
1102-
argsHtml = '<div class="prompt-card-args">'
1103-
+ p.arguments.map(function(a) {
1104-
return '<div class="prompt-arg-row">'
1105-
+ '<span class="prompt-arg-label">' + esc(a.name)
1106-
+ (a.required ? '<span class="req">*</span>' : '')
1107-
+ '</span>'
1108-
+ '<input class="prompt-arg-input" data-prompt="' + idx
1109-
+ '" data-arg="' + esc(a.name)
1110-
+ '" placeholder="' + esc(a.description || a.name) + '">'
1111-
+ '</div>';
1112-
}).join('')
1113-
+ '</div>';
1141+
var html = '';
1142+
var hasMultipleSections = workflows.length > 0 && quickActions.length > 0;
1143+
1144+
if (workflows.length > 0) {
1145+
if (hasMultipleSections) {
1146+
html += '<div class="prompt-section-header">Workflows</div>';
11141147
}
1115-
return '<div class="prompt-card">'
1116-
+ '<div class="prompt-card-header">'
1117-
+ '<span class="prompt-card-name">' + esc(p.name) + '</span>'
1118-
+ '<button class="prompt-copy-btn" onclick="copyPrompt(' + idx + ', this)">Copy</button>'
1119-
+ '</div>'
1120-
+ (p.description ? '<div class="prompt-card-desc">' + esc(p.description) + '</div>' : '')
1121-
+ argsHtml
1148+
html += workflows.map(renderPromptCard).join('');
1149+
}
1150+
if (quickActions.length > 0) {
1151+
if (hasMultipleSections) {
1152+
html += '<div class="prompt-section-header">Quick Actions</div>';
1153+
}
1154+
html += quickActions.map(renderPromptCard).join('');
1155+
}
1156+
el.innerHTML = html;
1157+
}
1158+
1159+
function renderPromptCard(p) {
1160+
var idx = p._idx;
1161+
var argsHtml = '';
1162+
if (p.arguments && p.arguments.length) {
1163+
argsHtml = '<div class="prompt-card-args">'
1164+
+ p.arguments.map(function(a) {
1165+
return '<div class="prompt-arg-row">'
1166+
+ '<span class="prompt-arg-label">' + esc(a.name)
1167+
+ (a.required ? '<span class="req">*</span>' : '')
1168+
+ '</span>'
1169+
+ '<input class="prompt-arg-input" data-prompt="' + idx
1170+
+ '" data-arg="' + esc(a.name)
1171+
+ '" placeholder="' + esc(a.description || a.name) + '">'
1172+
+ '</div>';
1173+
}).join('')
11221174
+ '</div>';
1123-
}).join('');
1175+
}
1176+
return '<div class="prompt-card">'
1177+
+ '<div class="prompt-card-header">'
1178+
+ '<div>'
1179+
+ '<div class="prompt-card-title">' + esc(humanize(p.name)) + '</div>'
1180+
+ '<div class="prompt-card-slug">' + esc(p.name) + '</div>'
1181+
+ '</div>'
1182+
+ '<button class="prompt-copy-btn" onclick="copyPrompt(' + idx + ', this)">Copy</button>'
1183+
+ '</div>'
1184+
+ (p.description ? '<div class="prompt-card-desc">' + esc(p.description) + '</div>' : '')
1185+
+ argsHtml
1186+
+ '</div>';
11241187
}
11251188

11261189
function copyPrompt(idx, btn) {
11271190
var p = _prompts[idx];
11281191
if (!p) { return; }
1129-
// Resolve prompt name as the text to copy (users paste as /prompt invocation)
11301192
var text = p.name;
1131-
// If prompt has args, collect values and append
11321193
if (p.arguments && p.arguments.length) {
11331194
var inputs = document.querySelectorAll('input[data-prompt="' + idx + '"]');
11341195
var parts = [];

apps/test-preview-data.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,55 @@
3434
"display_name": "Administrator",
3535
"description": "Full platform access for demo"
3636
},
37+
"prompts": [
38+
{
39+
"name": "explore-available-data",
40+
"description": "Discover what data is available about a topic",
41+
"category": "workflow",
42+
"arguments": [
43+
{"name": "topic", "description": "What topic or subject area?", "required": true}
44+
]
45+
},
46+
{
47+
"name": "create-interactive-dashboard",
48+
"description": "Discover data, build a visualization, and save it as a shareable asset",
49+
"category": "workflow",
50+
"arguments": [
51+
{"name": "topic", "description": "What should the dashboard visualize?", "required": true}
52+
]
53+
},
54+
{
55+
"name": "create-a-report",
56+
"description": "Analyze data and produce a structured Markdown report",
57+
"category": "workflow",
58+
"arguments": [
59+
{"name": "topic", "description": "What should the report cover?", "required": true}
60+
]
61+
},
62+
{
63+
"name": "trace-data-lineage",
64+
"description": "Trace where data comes from and what depends on it",
65+
"category": "workflow",
66+
"arguments": [
67+
{"name": "dataset", "description": "Which dataset or column to trace?", "required": true}
68+
]
69+
},
70+
{
71+
"name": "save-this-as-an-asset",
72+
"description": "Save an artifact from this conversation as a viewable, shareable asset",
73+
"category": "toolkit"
74+
},
75+
{
76+
"name": "show-my-saved-assets",
77+
"description": "Browse your saved artifacts and assets",
78+
"category": "toolkit"
79+
},
80+
{
81+
"name": "capture-this-as-knowledge",
82+
"description": "Record insights from this conversation for data catalog improvement",
83+
"category": "toolkit"
84+
}
85+
],
3786
"features": {
3887
"semantic_enrichment": true,
3988
"query_enrichment": true,

pkg/platform/prompts.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const (
2727
func (p *Platform) registerPlatformPrompts() {
2828
p.registerAutoPrompt()
2929
for _, promptCfg := range p.config.Server.Prompts {
30-
p.registerPrompt(promptCfg)
30+
p.registerPromptWithCategory(promptCfg, "custom")
3131
}
3232
p.registerWorkflowPrompts()
3333
}
@@ -58,10 +58,8 @@ func (p *Platform) registerAutoPrompt() {
5858
return buildPromptResult(content), nil
5959
})
6060

61-
p.promptInfos = append(p.promptInfos, registry.PromptInfo{
62-
Name: autoPromptName,
63-
Description: "Overview of this data platform — what it covers and how to use it",
64-
})
61+
// platform-overview is auto-invoked; it is not included in promptInfos
62+
// because copy-to-clipboard makes no sense for it.
6563
}
6664

6765
// buildDynamicOverviewContent builds the platform overview content dynamically
@@ -127,9 +125,10 @@ func (p *Platform) collectCapabilityBullets() []string {
127125
return caps
128126
}
129127

130-
// registerPrompt registers a single prompt with the MCP server,
131-
// supporting argument substitution in content.
132-
func (p *Platform) registerPrompt(cfg PromptConfig) {
128+
// registerPromptWithCategory registers a single prompt with the MCP server,
129+
// supporting argument substitution in content. The category is stored in
130+
// prompt metadata for frontend grouping (e.g., "workflow", "custom", "toolkit").
131+
func (p *Platform) registerPromptWithCategory(cfg PromptConfig, category string) {
133132
promptContent := cfg.Content
134133

135134
// Build MCP prompt arguments
@@ -155,6 +154,7 @@ func (p *Platform) registerPrompt(cfg PromptConfig) {
155154
info := registry.PromptInfo{
156155
Name: cfg.Name,
157156
Description: cfg.Description,
157+
Category: category,
158158
}
159159
for _, arg := range cfg.Arguments {
160160
info.Arguments = append(info.Arguments, registry.PromptArgumentInfo{
@@ -277,7 +277,7 @@ func (p *Platform) registerWorkflowPrompts() {
277277
continue
278278
}
279279

280-
p.registerPrompt(wp.config)
280+
p.registerPromptWithCategory(wp.config, "workflow")
281281
}
282282
}
283283

pkg/platform/prompts_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,14 +546,15 @@ func TestPromptMetadataCollection(t *testing.T) {
546546
}
547547
assert.True(t, customFound, "custom-prompt should be in collected infos")
548548

549-
// Find platform-overview (auto-registered because Description is set)
550-
var overviewFound bool
549+
// platform-overview should NOT be in collected infos (excluded for copy UX)
551550
for _, info := range infos {
552-
if info.Name == autoPromptName {
553-
overviewFound = true
554-
}
551+
assert.NotEqual(t, autoPromptName, info.Name, "platform-overview should not be in collected infos")
552+
}
553+
554+
// Verify categories are set
555+
for _, info := range infos {
556+
assert.NotEmpty(t, info.Category, "prompt %q should have a category", info.Name)
555557
}
556-
assert.True(t, overviewFound, "platform-overview should be in collected infos")
557558
}
558559

559560
func TestCollectToolkitPromptInfos(t *testing.T) {

pkg/registry/toolkit.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type AggregateToolkitFactory func(defaultName string, instances map[string]map[s
4848
type PromptInfo struct {
4949
Name string `json:"name"`
5050
Description string `json:"description"`
51+
Category string `json:"category,omitempty"`
5152
Arguments []PromptArgumentInfo `json:"arguments,omitempty"`
5253
}
5354

pkg/toolkits/knowledge/toolkit.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,10 +787,12 @@ func (*Toolkit) PromptInfos() []registry.PromptInfo {
787787
{
788788
Name: promptName,
789789
Description: "Guidance on when and how to capture domain knowledge insights",
790+
Category: "toolkit",
790791
},
791792
{
792793
Name: userPromptName,
793794
Description: "Record insights from this conversation for data catalog improvement",
795+
Category: "toolkit",
794796
},
795797
}
796798
}

pkg/toolkits/knowledge/toolkit_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3020,9 +3020,11 @@ func TestPromptInfos(t *testing.T) {
30203020

30213021
assert.Equal(t, promptName, infos[0].Name)
30223022
assert.NotEmpty(t, infos[0].Description)
3023+
assert.Equal(t, "toolkit", infos[0].Category)
30233024

30243025
assert.Equal(t, userPromptName, infos[1].Name)
30253026
assert.NotEmpty(t, infos[1].Description)
3027+
assert.Equal(t, "toolkit", infos[1].Category)
30263028
}
30273029

30283030
func TestRegisterPrompts(t *testing.T) {

pkg/toolkits/portal/toolkit.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,12 @@ func (*Toolkit) PromptInfos() []registry.PromptInfo {
191191
{
192192
Name: saveAssetPromptName,
193193
Description: "Save an artifact from this conversation as a viewable, shareable asset",
194+
Category: "toolkit",
194195
},
195196
{
196197
Name: showAssetsPromptName,
197198
Description: "Browse your saved artifacts and assets",
199+
Category: "toolkit",
198200
},
199201
}
200202
}

pkg/toolkits/portal/toolkit_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,9 +863,11 @@ func TestPromptInfos(t *testing.T) {
863863

864864
assert.Equal(t, saveAssetPromptName, infos[0].Name)
865865
assert.NotEmpty(t, infos[0].Description)
866+
assert.Equal(t, "toolkit", infos[0].Category)
866867

867868
assert.Equal(t, showAssetsPromptName, infos[1].Name)
868869
assert.NotEmpty(t, infos[1].Description)
870+
assert.Equal(t, "toolkit", infos[1].Category)
869871
}
870872

871873
func TestRegisterPrompts(t *testing.T) {

0 commit comments

Comments
 (0)