@morya-ui/mcp 0.2.2 → 0.2.3
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.
- package/README.md +18 -4
- package/data/catalog.json +149 -5
- package/data/example-coverage.json +29 -4
- package/data/golden-pages/dashboard-page.vue +103 -0
- package/data/golden-pages/form-page.vue +103 -0
- package/data/golden-pages/list-page.vue +117 -0
- package/dist/__tests__/page-snippets.test.d.ts +1 -0
- package/dist/__tests__/page-snippets.test.js +17 -0
- package/dist/__tests__/tools.test.js +34 -1
- package/dist/catalog.js +14 -0
- package/dist/decisions.js +121 -0
- package/dist/golden-pages.d.ts +16 -0
- package/dist/golden-pages.js +67 -0
- package/dist/index.js +22 -1
- package/dist/page-snippets.d.ts +33 -0
- package/dist/page-snippets.js +405 -0
- package/dist/patterns.d.ts +20 -0
- package/dist/patterns.js +71 -18
- package/dist/tools.d.ts +54 -1
- package/dist/tools.js +262 -84
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { findComponent, findGuide, loadCatalog, normalizeName, resolveLocale, textResult, toKebab, } from './catalog.js';
|
|
2
2
|
import { componentDecisions, findDecision, scoreDecision } from './decisions.js';
|
|
3
|
+
import { listGoldenPages, readGoldenPageSource } from './golden-pages.js';
|
|
4
|
+
import { filterPageSnippets, findPageSnippet, pageSnippets, scorePageSnippet } from './page-snippets.js';
|
|
3
5
|
import { designRules, findPattern, pagePatterns, scorePattern } from './patterns.js';
|
|
4
6
|
import { countCatalogResourceTemplates, countCatalogResources } from './resources.js';
|
|
5
7
|
function inspectButtonIconOnlyUsage(code, issues) {
|
|
@@ -60,16 +62,19 @@ function generatedPageCode(patternId, intent, locale) {
|
|
|
60
62
|
const layoutImports = useLayoutShell
|
|
61
63
|
? ', MLayout, MLayoutContent, MLayoutHeader, MLayoutSider, MBreadcrumb'
|
|
62
64
|
: '';
|
|
65
|
+
const pageImports = useLayoutShell
|
|
66
|
+
? ', MPageContent, MPageFilters, MPageHeader, MPageSection, MPageToolbar'
|
|
67
|
+
: '';
|
|
63
68
|
const listImports = isList ? ', MSelect, MSpace, MTable' : '';
|
|
64
69
|
const formImports = isForm || isAuth || isWizard ? ', MForm, MFormItem, MSelect' : '';
|
|
65
|
-
const dashboardImports = isDashboard ? ', MCard, MGrid, MGridItem, MSkeleton, MTable' : '';
|
|
70
|
+
const dashboardImports = isDashboard ? ', MCard, MGrid, MGridItem, MPagePlaceholder, MPageStat, MSkeleton, MTable' : '';
|
|
66
71
|
const detailImports = isDetail ? ', MDivider' : '';
|
|
67
72
|
const emptyImports = isEmpty ? ', MDataView' : '';
|
|
68
73
|
const wizardImports = isWizard ? ', MStepper' : '';
|
|
69
74
|
const settingsImports = isSettings ? ', MTabs' : '';
|
|
70
75
|
const script = `<script setup lang="ts">
|
|
71
76
|
import { ref } from 'vue'
|
|
72
|
-
import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports} } from 'morya-ui'
|
|
77
|
+
import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports} } from 'morya-ui'
|
|
73
78
|
|
|
74
79
|
const loading = ref(false)
|
|
75
80
|
const error = ref('')
|
|
@@ -94,62 +99,55 @@ async function submit() {
|
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
</script>`;
|
|
97
|
-
const listContent = `
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const dashboardContent = `
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<MCard>
|
|
147
|
-
<MDivider />
|
|
148
|
-
<dl class="m-generated-details">
|
|
149
|
-
<div><dt>${zh ? '名称' : 'Name'}</dt><dd>${zh ? '示例资源' : 'Example resource'}</dd></div>
|
|
150
|
-
<div><dt>${zh ? '更新时间' : 'Updated'}</dt><dd>—</dd></div>
|
|
151
|
-
</dl>
|
|
152
|
-
</MCard>`;
|
|
102
|
+
const listContent = ` <MPageFilters :aria-label="${zh ? '筛选' : 'Filters'}">
|
|
103
|
+
<MSpace wrap>
|
|
104
|
+
<MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
|
|
105
|
+
<MButton severity="primary">${zh ? '查询' : 'Search'}</MButton>
|
|
106
|
+
<MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
|
|
107
|
+
</MSpace>
|
|
108
|
+
</MPageFilters>
|
|
109
|
+
<MPageToolbar :title="title">
|
|
110
|
+
<template #actions>
|
|
111
|
+
<MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
|
|
112
|
+
</template>
|
|
113
|
+
</MPageToolbar>
|
|
114
|
+
<MTable :columns="columns" :rows="rows" :loading="loading" paginator :rows-per-page="10" striped bordered row-key="id">
|
|
115
|
+
<template #empty>
|
|
116
|
+
<p style="margin:0;padding:var(--m-space-8);text-align:center;color:var(--m-color-text-muted)">${zh ? '暂无数据' : 'No data yet'}</p>
|
|
117
|
+
</template>
|
|
118
|
+
</MTable>`;
|
|
119
|
+
const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
|
|
120
|
+
<MPageSection variant="form">
|
|
121
|
+
<MForm @submit.prevent="submit">
|
|
122
|
+
<MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
|
|
123
|
+
<MInput v-model="model.name" fluid />
|
|
124
|
+
</MFormItem>
|
|
125
|
+
<MPageSection variant="actions">
|
|
126
|
+
<MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
|
|
127
|
+
<MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
|
|
128
|
+
</MPageSection>
|
|
129
|
+
</MForm>
|
|
130
|
+
</MPageSection>`;
|
|
131
|
+
const dashboardContent = ` <MPageHeader :title="title" />
|
|
132
|
+
<MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
|
|
133
|
+
<MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
|
|
134
|
+
<MPageStat :label="metric.label" :value="metric.value" icon="activity" />
|
|
135
|
+
</MGridItem>
|
|
136
|
+
</MGrid>
|
|
137
|
+
<MCard :title="${zh ? '趋势概览' : 'Trend overview'}">
|
|
138
|
+
<MSkeleton v-if="loading" height="8rem" />
|
|
139
|
+
<MPagePlaceholder v-else :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}" aria-label="${zh ? '图表占位' : 'Chart placeholder'}" />
|
|
140
|
+
</MCard>`;
|
|
141
|
+
const detailContent = ` <MPageToolbar :title="title">
|
|
142
|
+
<template #actions>
|
|
143
|
+
<MButton severity="primary" outlined>${zh ? '编辑' : 'Edit'}</MButton>
|
|
144
|
+
</template>
|
|
145
|
+
</MPageToolbar>
|
|
146
|
+
<MTag value="${zh ? '正常' : 'Active'}" severity="success" />
|
|
147
|
+
<MCard>
|
|
148
|
+
<MDivider />
|
|
149
|
+
<p style="margin:0;color:var(--m-color-text-muted)">${zh ? '示例资源详情' : 'Example resource details'}</p>
|
|
150
|
+
</MCard>`;
|
|
153
151
|
const settingsContent = ` <h1 class="m-generated-title">${title}</h1>
|
|
154
152
|
<MTabs :value="'general'" :items="[{ label: '${zh ? '常规' : 'General'}', value: 'general' }]" />
|
|
155
153
|
<MForm class="m-generated-form" @submit.prevent="submit">
|
|
@@ -161,14 +159,16 @@ async function submit() {
|
|
|
161
159
|
let innerTemplate = '';
|
|
162
160
|
if (isList) {
|
|
163
161
|
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
164
|
-
<MLayout has-sider
|
|
165
|
-
<MLayoutSider
|
|
162
|
+
<MLayout has-sider fill-viewport>
|
|
163
|
+
<MLayoutSider bordered />
|
|
166
164
|
<MLayout>
|
|
167
|
-
<MLayoutHeader
|
|
165
|
+
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
168
166
|
<MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
|
|
169
167
|
</MLayoutHeader>
|
|
170
|
-
<MLayoutContent
|
|
168
|
+
<MLayoutContent>
|
|
169
|
+
<MPageContent>
|
|
171
170
|
${listContent}
|
|
171
|
+
</MPageContent>
|
|
172
172
|
</MLayoutContent>
|
|
173
173
|
</MLayout>
|
|
174
174
|
</MLayout>
|
|
@@ -176,13 +176,16 @@ ${listContent}
|
|
|
176
176
|
}
|
|
177
177
|
else if (useLayoutShell) {
|
|
178
178
|
const content = isDashboard ? dashboardContent : isDetail ? detailContent : isSettings ? settingsContent : formContent;
|
|
179
|
+
const pageContentAttrs = isForm || isSettings ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
|
|
179
180
|
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
180
|
-
<MLayout
|
|
181
|
-
<MLayoutHeader
|
|
181
|
+
<MLayout fill-viewport>
|
|
182
|
+
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
182
183
|
<MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
|
|
183
184
|
</MLayoutHeader>
|
|
184
|
-
<MLayoutContent
|
|
185
|
+
<MLayoutContent>
|
|
186
|
+
<MPageContent${pageContentAttrs}>
|
|
185
187
|
${content}
|
|
188
|
+
</MPageContent>
|
|
186
189
|
</MLayoutContent>
|
|
187
190
|
</MLayout>
|
|
188
191
|
</MConfigProvider>`;
|
|
@@ -249,24 +252,9 @@ ${content}
|
|
|
249
252
|
<p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
|
|
250
253
|
</template>`;
|
|
251
254
|
const style = `<style scoped>
|
|
252
|
-
.m-generated-
|
|
253
|
-
.m-generated-sider { border-right: 1px solid var(--m-color-border); }
|
|
254
|
-
.m-generated-header { padding: var(--m-space-4) var(--m-space-6); border-bottom: 1px solid var(--m-color-border); }
|
|
255
|
-
.m-generated-content { padding: var(--m-space-6); display: flex; flex-direction: column; gap: var(--m-space-4); }
|
|
256
|
-
.m-generated-filters { padding: var(--m-space-4); background: color-mix(in srgb, var(--m-color-border) 25%, transparent); border-radius: var(--m-radius-md); border: 1px solid var(--m-color-border); }
|
|
257
|
-
.m-generated-toolbar, .m-generated-actions { display: flex; gap: var(--m-space-3); align-items: center; justify-content: space-between; flex-wrap: wrap; }
|
|
258
|
-
.m-generated-title { margin: 0; font-size: var(--m-font-size-lg); font-weight: 600; color: var(--m-color-text); }
|
|
259
|
-
.m-generated-intro { margin-bottom: var(--m-space-2); }
|
|
260
|
-
.m-generated-form { padding: var(--m-space-6); border: 1px solid var(--m-color-border); border-radius: var(--m-radius-md); box-shadow: var(--m-shadow-sm); }
|
|
261
|
-
.m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; }
|
|
262
|
-
.m-generated-metric { display: block; font-size: var(--m-font-size-lg); margin: var(--m-space-2) 0; }
|
|
263
|
-
.m-generated-details { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--m-space-4); margin: 0; }
|
|
264
|
-
.m-generated-details dt { color: var(--m-color-text-muted); font-size: var(--m-font-size-sm); }
|
|
265
|
-
.m-generated-details dd { margin: var(--m-space-1) 0 0; }
|
|
255
|
+
.m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
|
|
266
256
|
.m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
|
|
267
|
-
.m-generated-muted { margin: 0; color: var(--m-color-text-muted); }
|
|
268
257
|
.m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
|
|
269
|
-
@media (max-width: 48rem) { .m-generated-content { padding: var(--m-space-4); } .m-generated-details { grid-template-columns: 1fr; } }
|
|
270
258
|
</style>`;
|
|
271
259
|
return { script, template, style };
|
|
272
260
|
}
|
|
@@ -430,6 +418,20 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
430
418
|
}
|
|
431
419
|
}
|
|
432
420
|
}
|
|
421
|
+
if (scope === 'all' || scope === 'snippets') {
|
|
422
|
+
for (const snippet of pageSnippets) {
|
|
423
|
+
const score = scorePageSnippet(snippet, query);
|
|
424
|
+
if (score > 0) {
|
|
425
|
+
hits.push({
|
|
426
|
+
type: 'snippet',
|
|
427
|
+
id: snippet.id,
|
|
428
|
+
title: locale === 'en-US' ? snippet.titleEn : snippet.title,
|
|
429
|
+
score,
|
|
430
|
+
snippet: locale === 'en-US' ? snippet.descriptionEn : snippet.description,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
433
435
|
if (scope === 'all' || scope === 'patterns') {
|
|
434
436
|
for (const pattern of pagePatterns) {
|
|
435
437
|
const score = scorePattern(pattern, query);
|
|
@@ -678,9 +680,14 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
678
680
|
interactionRules: best.pattern.interactionRules,
|
|
679
681
|
avoid: best.pattern.avoid,
|
|
680
682
|
alternatives: ranked.slice(1, 3).filter((item) => item.score > 0).map((item) => ({ id: item.pattern.id, score: item.score })),
|
|
681
|
-
nextStep:
|
|
682
|
-
|
|
683
|
-
|
|
683
|
+
nextStep: (() => {
|
|
684
|
+
const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
|
|
685
|
+
listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
|
|
686
|
+
best.pattern.id;
|
|
687
|
+
return locale === 'en-US'
|
|
688
|
+
? `Call get_golden_page("${goldenId}"), get_pattern, get_design_rules, then get_component/get_example. Pass includeScaffold: true for MPage* starter code. Run validate_page on the result.`
|
|
689
|
+
: `调用 get_golden_page("${goldenId}")、get_pattern、get_design_rules,再用 get_component/get_example 查 API。需要 starter 时传 includeScaffold: true(已使用 MPage* 组件)。生成后请 validate_page 校验。`;
|
|
690
|
+
})(),
|
|
684
691
|
};
|
|
685
692
|
if (args.includeScaffold) {
|
|
686
693
|
const code = generatedPageCode(best.pattern.id, args.intent, locale);
|
|
@@ -697,6 +704,166 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
697
704
|
}
|
|
698
705
|
return textResult(result);
|
|
699
706
|
}
|
|
707
|
+
function getGoldenPage(args) {
|
|
708
|
+
const locale = resolveLocale(args.mode);
|
|
709
|
+
const payload = readGoldenPageSource(args.page);
|
|
710
|
+
if (!payload) {
|
|
711
|
+
return textResult({
|
|
712
|
+
error: `Golden page not found: ${args.page}`,
|
|
713
|
+
availablePages: listGoldenPages().map((item) => item.id),
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
return textResult({
|
|
717
|
+
id: payload.record.id,
|
|
718
|
+
patternId: payload.record.patternId,
|
|
719
|
+
title: locale === 'en-US' ? payload.record.titleEn : payload.record.title,
|
|
720
|
+
file: payload.record.file,
|
|
721
|
+
source: payload.source,
|
|
722
|
+
nextStep: locale === 'en-US'
|
|
723
|
+
? 'Copy this structure and replace business data. Prefer MPage* components over custom scoped CSS.'
|
|
724
|
+
: '复制此结构并替换业务数据。优先使用 MPage* 组件,而不是自定义 scoped CSS。',
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
function listGoldenPageCatalog(args = {}) {
|
|
728
|
+
const locale = resolveLocale(args.mode);
|
|
729
|
+
return textResult({
|
|
730
|
+
items: listGoldenPages().map((item) => ({
|
|
731
|
+
id: item.id,
|
|
732
|
+
patternId: item.patternId,
|
|
733
|
+
title: locale === 'en-US' ? item.titleEn : item.title,
|
|
734
|
+
file: item.file,
|
|
735
|
+
})),
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
function listPageSnippets(args) {
|
|
739
|
+
const locale = resolveLocale(args.mode);
|
|
740
|
+
const result = filterPageSnippets(args);
|
|
741
|
+
return textResult({
|
|
742
|
+
...result,
|
|
743
|
+
items: result.items.map((item) => ({
|
|
744
|
+
id: item.id,
|
|
745
|
+
title: locale === 'en-US' ? item.titleEn : item.title,
|
|
746
|
+
description: locale === 'en-US' ? item.descriptionEn : item.description,
|
|
747
|
+
pageTypes: item.pageTypes,
|
|
748
|
+
keywords: item.keywords,
|
|
749
|
+
imports: item.imports,
|
|
750
|
+
})),
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
function getPageSnippet(args) {
|
|
754
|
+
const locale = resolveLocale(args.mode);
|
|
755
|
+
const ranked = pageSnippets
|
|
756
|
+
.map((snippet) => ({ snippet, score: scorePageSnippet(snippet, args.section) }))
|
|
757
|
+
.sort((a, b) => b.score - a.score || a.snippet.id.localeCompare(b.snippet.id));
|
|
758
|
+
const match = findPageSnippet(args.section) || (ranked[0]?.score ? ranked[0].snippet : undefined);
|
|
759
|
+
if (!match || (ranked[0]?.score ?? 0) === 0) {
|
|
760
|
+
return textResult({
|
|
761
|
+
error: `Page snippet not found: ${args.section}`,
|
|
762
|
+
availableSnippets: pageSnippets.map((item) => item.id),
|
|
763
|
+
suggestion: locale === 'en-US'
|
|
764
|
+
? 'Try list_page_snippets with query like "filters", "toolbar", or pageType="list".'
|
|
765
|
+
: '可尝试 list_page_snippets,query 如 "filters"、"toolbar",或 pageType="list"。',
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
const imports = [...new Set(match.imports)].sort();
|
|
769
|
+
const payload = {
|
|
770
|
+
id: match.id,
|
|
771
|
+
title: locale === 'en-US' ? match.titleEn : match.title,
|
|
772
|
+
description: locale === 'en-US' ? match.descriptionEn : match.description,
|
|
773
|
+
pageTypes: match.pageTypes,
|
|
774
|
+
imports,
|
|
775
|
+
template: match.template,
|
|
776
|
+
rules: locale === 'en-US' ? match.rulesEn : match.rules,
|
|
777
|
+
avoid: locale === 'en-US' ? match.avoidEn : match.avoid,
|
|
778
|
+
nextStep: locale === 'en-US'
|
|
779
|
+
? 'Merge this block into the existing page. Call get_component/get_example for unfamiliar imports, then validate_usage and validate_page.'
|
|
780
|
+
: '将此区块合并进现有页面。不熟悉的 import 用 get_component/get_example 核对,完成后 validate_usage / validate_page。',
|
|
781
|
+
};
|
|
782
|
+
if (match.scriptSetup)
|
|
783
|
+
payload.scriptSetup = match.scriptSetup;
|
|
784
|
+
if (args.includeScript) {
|
|
785
|
+
payload.vue = {
|
|
786
|
+
imports: `import { ${imports.join(', ')} } from 'morya-ui'`,
|
|
787
|
+
scriptSetup: match.scriptSetup || '',
|
|
788
|
+
template: match.template,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
if (!findPageSnippet(args.section) && ranked.length > 1 && ranked[0]?.score) {
|
|
792
|
+
payload.matchedBy = 'query';
|
|
793
|
+
payload.alternatives = ranked
|
|
794
|
+
.slice(1, 4)
|
|
795
|
+
.filter((entry) => entry.score > 0)
|
|
796
|
+
.map((entry) => ({ id: entry.snippet.id, score: entry.score }));
|
|
797
|
+
}
|
|
798
|
+
return textResult(payload);
|
|
799
|
+
}
|
|
800
|
+
function validatePage(args) {
|
|
801
|
+
const locale = resolveLocale(args.mode);
|
|
802
|
+
const code = args.code || '';
|
|
803
|
+
const issues = [];
|
|
804
|
+
if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(code)) {
|
|
805
|
+
issues.push({
|
|
806
|
+
type: 'raw-color',
|
|
807
|
+
message: locale === 'en-US'
|
|
808
|
+
? 'Use --m-* design tokens instead of hex colors.'
|
|
809
|
+
: '请使用 --m-* 设计令牌,不要写 hex 色值。',
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
if (/\brgb\s*\(/i.test(code) && !/--m-/.test(code)) {
|
|
813
|
+
issues.push({
|
|
814
|
+
type: 'raw-color',
|
|
815
|
+
message: locale === 'en-US'
|
|
816
|
+
? 'Use --m-* tokens instead of raw rgb()/rgba() page styles.'
|
|
817
|
+
: '页面样式请使用 --m-* 令牌,不要写裸 rgb()/rgba()。',
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
if (/<MCard\b[^>]*>[\s\S]*?<MTable\b[^>]*\bbordered\b/i.test(code)) {
|
|
821
|
+
issues.push({
|
|
822
|
+
type: 'double-border',
|
|
823
|
+
message: locale === 'en-US'
|
|
824
|
+
? 'Avoid wrapping bordered MTable with MCard; place MTable directly in MPageContent.'
|
|
825
|
+
: '避免用 MCard 包裹 bordered 的 MTable;表格应直接放在 MPageContent 内。',
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
if (/<MLayoutContent\b/i.test(code) && !/<MPageContent\b/i.test(code)) {
|
|
829
|
+
issues.push({
|
|
830
|
+
type: 'missing-page-content',
|
|
831
|
+
message: locale === 'en-US'
|
|
832
|
+
? 'Prefer MPageContent inside MLayoutContent for consistent page spacing.'
|
|
833
|
+
: '建议在 MLayoutContent 内使用 MPageContent 统一页面间距。',
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
if ((/\bpage[-_](?:list|form|dashboard)?[-_]?(?:filters|toolbar|content|header)\b/i.test(code) ||
|
|
837
|
+
/class="[^"]*filters/i.test(code)) &&
|
|
838
|
+
!/<MPage(?:Content|Filters|Toolbar|Header|Section|Stat)\b/i.test(code)) {
|
|
839
|
+
issues.push({
|
|
840
|
+
type: 'custom-page-css',
|
|
841
|
+
message: locale === 'en-US'
|
|
842
|
+
? 'Replace custom page section CSS with MPageFilters, MPageToolbar, MPageHeader, or MPageSection.'
|
|
843
|
+
: '请用 MPageFilters / MPageToolbar / MPageHeader / MPageSection 替代自定义页面区块 CSS。',
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
if (/<MPageFilters\b/i.test(code) && /<MCard\b[^>]*>[\s\S]*?<MPageFilters\b/i.test(code)) {
|
|
847
|
+
issues.push({
|
|
848
|
+
type: 'redundant-wrapper',
|
|
849
|
+
message: locale === 'en-US'
|
|
850
|
+
? 'Do not wrap MPageFilters in MCard; MPageFilters already provides the surface.'
|
|
851
|
+
: '不要用 MCard 包裹 MPageFilters;MPageFilters 已自带表面样式。',
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
return textResult({
|
|
855
|
+
ok: issues.length === 0,
|
|
856
|
+
issues,
|
|
857
|
+
recommendations: locale === 'en-US'
|
|
858
|
+
? designRules.composition.workflow
|
|
859
|
+
: [
|
|
860
|
+
'生成页面前先调用 recommend_page 与 get_golden_page。',
|
|
861
|
+
'MLayout 使用 fillViewport;MLayoutContent 内放 MPageContent。',
|
|
862
|
+
'筛选区用 MPageFilters,标题+操作用 MPageToolbar,表单用 MPageSection。',
|
|
863
|
+
'列表表格不要额外包 MCard。',
|
|
864
|
+
],
|
|
865
|
+
});
|
|
866
|
+
}
|
|
700
867
|
function getDesignRules(args = {}) {
|
|
701
868
|
const locale = resolveLocale(args.mode);
|
|
702
869
|
if (locale === 'zh-CN')
|
|
@@ -709,6 +876,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
709
876
|
typography: '--m-font-size-xs/sm/md/lg',
|
|
710
877
|
motion: '--m-motion-fast/normal',
|
|
711
878
|
},
|
|
879
|
+
composition: designRules.composition,
|
|
712
880
|
actions: {
|
|
713
881
|
primary: { component: 'MButton', props: ['omit severity or use primary'] },
|
|
714
882
|
secondary: { component: 'MButton', props: ['severity="secondary"', 'outlined or text'] },
|
|
@@ -952,6 +1120,11 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
952
1120
|
'recommend_page',
|
|
953
1121
|
'get_design_rules',
|
|
954
1122
|
'recommend_component',
|
|
1123
|
+
'list_golden_pages',
|
|
1124
|
+
'get_golden_page',
|
|
1125
|
+
'list_page_snippets',
|
|
1126
|
+
'get_page_snippet',
|
|
1127
|
+
'validate_page',
|
|
955
1128
|
'version',
|
|
956
1129
|
],
|
|
957
1130
|
});
|
|
@@ -970,6 +1143,11 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
970
1143
|
recommendPage,
|
|
971
1144
|
getDesignRules,
|
|
972
1145
|
recommendComponent,
|
|
1146
|
+
listGoldenPageCatalog,
|
|
1147
|
+
getGoldenPage,
|
|
1148
|
+
listPageSnippets,
|
|
1149
|
+
getPageSnippet,
|
|
1150
|
+
validatePage,
|
|
973
1151
|
version,
|
|
974
1152
|
};
|
|
975
1153
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morya-ui/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.3",
|
|
5
5
|
"description": "MCP server for morya-ui — component docs, examples, and usage guidance for AI coding agents.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/ui-mcp#readme",
|