@morya-ui/mcp 0.2.9 → 0.3.0
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 +1 -1
- package/data/catalog.json +470 -189
- package/data/example-coverage.json +67 -39
- package/data/golden-pages/detail-page.vue +178 -0
- package/data/golden-pages/form-in-dialog.vue +166 -0
- package/data/golden-pages/result-page.vue +84 -0
- package/data/golden-pages/settings-page.vue +163 -0
- package/data/golden-pages/wizard-form.vue +182 -0
- package/dist/__tests__/tools.test.js +87 -4
- package/dist/golden-pages.js +35 -0
- package/dist/index.js +1 -1
- package/dist/page-snippets.js +21 -21
- package/dist/patterns.d.ts +3 -3
- package/dist/patterns.js +25 -6
- package/dist/tools.js +158 -55
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -57,7 +57,7 @@ function generatedPageCode(patternId, intent, locale) {
|
|
|
57
57
|
const isSettings = patternId === 'settings-page';
|
|
58
58
|
const isAuth = patternId === 'auth-page';
|
|
59
59
|
const isForm = patternId === 'form-page' || isSettings;
|
|
60
|
-
const useLayoutShell = isList || isForm || isDashboard || isDetail || isSettings;
|
|
60
|
+
const useLayoutShell = isList || isForm || isDashboard || isDetail || isSettings || isWizard;
|
|
61
61
|
const title = intent || (zh ? '业务页面' : 'Business page');
|
|
62
62
|
const layoutImports = useLayoutShell
|
|
63
63
|
? ', MLayout, MLayoutContent, MLayoutHeader, MLayoutSider, MBreadcrumb'
|
|
@@ -68,9 +68,9 @@ function generatedPageCode(patternId, intent, locale) {
|
|
|
68
68
|
const listImports = isList ? ', MEmpty, MSelect, MSpace, MTable' : '';
|
|
69
69
|
const formImports = isForm || isAuth || isWizard ? ', MForm, MFormItem, MSelect' : '';
|
|
70
70
|
const dashboardImports = isDashboard ? ', MCard, MGrid, MGridItem, MPagePlaceholder, MPageStat, MSkeleton, MTable' : '';
|
|
71
|
-
const detailImports = isDetail ? ',
|
|
71
|
+
const detailImports = isDetail ? ', MSpace' : '';
|
|
72
72
|
const emptyImports = isEmpty ? ', MEmpty, MPageToolbar' : '';
|
|
73
|
-
const wizardImports = isWizard ? ', MStepper' : '';
|
|
73
|
+
const wizardImports = isWizard ? ', MStepper, MSpace' : '';
|
|
74
74
|
const settingsImports = isSettings ? ', MTabs' : '';
|
|
75
75
|
const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
|
|
76
76
|
const script = `<script setup lang="ts">
|
|
@@ -79,6 +79,7 @@ import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pa
|
|
|
79
79
|
|
|
80
80
|
const loading = ref(false)
|
|
81
81
|
const error = ref('')
|
|
82
|
+
const title = ${JSON.stringify(title)}
|
|
82
83
|
${isList ? `const keyword = ref('')
|
|
83
84
|
const rows = ref<Record<string, unknown>[]>([])
|
|
84
85
|
const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
|
|
@@ -87,7 +88,10 @@ const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'st
|
|
|
87
88
|
{ label: '${zh ? '总用户' : 'Users'}', value: '0' },
|
|
88
89
|
{ label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
|
|
89
90
|
])
|
|
91
|
+
` : ''}${isSettings ? `const activeTab = ref('general')
|
|
92
|
+
const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
|
|
90
93
|
` : ''}${isWizard ? `const activeStep = ref(0)
|
|
94
|
+
const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
|
|
91
95
|
` : ''}
|
|
92
96
|
|
|
93
97
|
async function submit() {
|
|
@@ -149,24 +153,48 @@ async function submit() {
|
|
|
149
153
|
<MSkeleton v-if="loading" height="8rem" />
|
|
150
154
|
<MPagePlaceholder v-else :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}" aria-label="${zh ? '图表占位' : 'Chart placeholder'}" />
|
|
151
155
|
</MCard>`;
|
|
152
|
-
const detailContent = ` <
|
|
156
|
+
const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
|
|
153
157
|
<template #actions>
|
|
154
|
-
<
|
|
158
|
+
<MSpace>
|
|
159
|
+
<MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
|
|
160
|
+
<MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
|
|
161
|
+
<MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
|
|
162
|
+
</MSpace>
|
|
155
163
|
</template>
|
|
156
|
-
</
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
<MDivider />
|
|
160
|
-
<p style="margin:0;color:var(--m-color-text-muted)">${zh ? '示例资源详情' : 'Example resource details'}</p>
|
|
164
|
+
</MPageHeader>
|
|
165
|
+
<MCard :title="${zh ? '基本信息' : 'Basics'}">
|
|
166
|
+
<p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
|
|
161
167
|
</MCard>`;
|
|
162
|
-
const settingsContent = `
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
|
|
169
|
+
<MTabs v-model="activeTab" :tabs="settingTabs">
|
|
170
|
+
<template #default="{ activeValue }">
|
|
171
|
+
<MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
|
|
172
|
+
<MForm @submit.prevent="submit">
|
|
173
|
+
<MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
|
|
174
|
+
<MInput v-model="model.name" fluid />
|
|
175
|
+
</MFormItem>
|
|
176
|
+
<MPageSection variant="actions">
|
|
177
|
+
<MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
|
|
178
|
+
</MPageSection>
|
|
179
|
+
</MForm>
|
|
180
|
+
</MPageSection>
|
|
181
|
+
</template>
|
|
182
|
+
</MTabs>`;
|
|
183
|
+
const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
|
|
184
|
+
<MStepper v-model="activeStep" :steps="wizardSteps" linear />
|
|
185
|
+
<MPageSection variant="form">
|
|
186
|
+
<MForm @submit.prevent="submit">
|
|
187
|
+
<MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
|
|
188
|
+
<MInput v-model="model.name" fluid />
|
|
189
|
+
</MFormItem>
|
|
190
|
+
<MPageSection variant="actions">
|
|
191
|
+
<MSpace>
|
|
192
|
+
<MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
|
|
193
|
+
<MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
|
|
194
|
+
</MSpace>
|
|
195
|
+
</MPageSection>
|
|
196
|
+
</MForm>
|
|
197
|
+
</MPageSection>`;
|
|
170
198
|
let innerTemplate = '';
|
|
171
199
|
if (isList) {
|
|
172
200
|
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
@@ -186,12 +214,20 @@ ${listContent}
|
|
|
186
214
|
</MConfigProvider>`;
|
|
187
215
|
}
|
|
188
216
|
else if (useLayoutShell) {
|
|
189
|
-
const content = isDashboard
|
|
190
|
-
|
|
217
|
+
const content = isDashboard
|
|
218
|
+
? dashboardContent
|
|
219
|
+
: isDetail
|
|
220
|
+
? detailContent
|
|
221
|
+
: isSettings
|
|
222
|
+
? settingsContent
|
|
223
|
+
: isWizard
|
|
224
|
+
? wizardContent
|
|
225
|
+
: formContent;
|
|
226
|
+
const pageContentAttrs = isForm || isSettings || isWizard ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
|
|
191
227
|
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
192
228
|
<MLayout fill-viewport>
|
|
193
229
|
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
194
|
-
<MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label:
|
|
230
|
+
<MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
|
|
195
231
|
</MLayoutHeader>
|
|
196
232
|
<MLayoutContent>
|
|
197
233
|
<MPageContent${pageContentAttrs}>
|
|
@@ -236,19 +272,6 @@ ${content}
|
|
|
236
272
|
</template>
|
|
237
273
|
</MEmpty>
|
|
238
274
|
</main>
|
|
239
|
-
</MConfigProvider>`;
|
|
240
|
-
}
|
|
241
|
-
else if (isWizard) {
|
|
242
|
-
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
243
|
-
<main class="m-generated-page">
|
|
244
|
-
<MCard>
|
|
245
|
-
<MStepper v-model="activeStep" :items="[${zh ? "'基本信息', '确认'" : "'Details', 'Confirm'"}]" />
|
|
246
|
-
<MForm label-position="top" @submit.prevent="submit">
|
|
247
|
-
<MFormItem label="${zh ? '名称' : 'Name'}" name="name"><MInput v-model="model.name" fluid /></MFormItem>
|
|
248
|
-
<MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
|
|
249
|
-
</MForm>
|
|
250
|
-
</MCard>
|
|
251
|
-
</main>
|
|
252
275
|
</MConfigProvider>`;
|
|
253
276
|
}
|
|
254
277
|
else {
|
|
@@ -697,24 +720,48 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
697
720
|
nextStep: (() => {
|
|
698
721
|
const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
|
|
699
722
|
listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
723
|
+
null;
|
|
724
|
+
if (locale === 'en-US') {
|
|
725
|
+
return goldenId
|
|
726
|
+
? `Call get_golden_page("${goldenId}") and mirror it. Look up unfamiliar APIs with get_component/get_example, then always run validate_usage and validate_page. includeScaffold returns the golden source when available — not a separate aesthetic template.`
|
|
727
|
+
: `No golden page for this pattern: use get_pattern + get_design_rules, look up APIs with get_component/get_example, then always run validate_usage and validate_page.`;
|
|
728
|
+
}
|
|
729
|
+
return goldenId
|
|
730
|
+
? `调用 get_golden_page("${goldenId}") 并镜像结构。不熟悉的 API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。includeScaffold 有黄金样例时返回样例源码,不是另一套审美模板。`
|
|
731
|
+
: `该模式暂无黄金样例:用 get_pattern + get_design_rules,API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。`;
|
|
704
732
|
})(),
|
|
705
733
|
};
|
|
706
734
|
if (args.includeScaffold) {
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
735
|
+
const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
|
|
736
|
+
listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
|
|
737
|
+
null;
|
|
738
|
+
const golden = goldenId ? readGoldenPageSource(goldenId) : null;
|
|
739
|
+
if (golden) {
|
|
740
|
+
result.scaffold = {
|
|
741
|
+
source: 'golden-page',
|
|
742
|
+
goldenPage: golden.record.id,
|
|
743
|
+
files: { component: golden.source },
|
|
744
|
+
warnings: [
|
|
745
|
+
locale === 'en-US'
|
|
746
|
+
? 'Scaffold is the golden page source. Remap domain copy/data only; never invent props. Then validate_usage + validate_page.'
|
|
747
|
+
: '脚手架即黄金样例源码。只替换域文案与数据,禁止臆造 prop。完成后必须 validate_usage + validate_page。',
|
|
748
|
+
],
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
const code = generatedPageCode(best.pattern.id, args.intent, locale);
|
|
753
|
+
const componentSource = `${code.script}\n\n${code.template}\n\n${code.style}`;
|
|
754
|
+
result.scaffold = {
|
|
755
|
+
source: 'generated-fallback',
|
|
756
|
+
vue: code,
|
|
757
|
+
files: { component: componentSource },
|
|
758
|
+
warnings: [
|
|
759
|
+
locale === 'en-US'
|
|
760
|
+
? 'No golden page for this pattern — structural fallback only. Prefer get_pattern + get_component. Must validate_usage before delivery.'
|
|
761
|
+
: '该模式暂无黄金样例,仅为结构占位。优先 get_pattern + get_component。交付前必须 validate_usage。',
|
|
762
|
+
],
|
|
763
|
+
};
|
|
764
|
+
}
|
|
718
765
|
}
|
|
719
766
|
return textResult(result);
|
|
720
767
|
}
|
|
@@ -734,8 +781,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
734
781
|
file: payload.record.file,
|
|
735
782
|
source: payload.source,
|
|
736
783
|
nextStep: locale === 'en-US'
|
|
737
|
-
? '
|
|
738
|
-
: '
|
|
784
|
+
? 'Mirror this structure and replace business data. Look up unfamiliar APIs with get_component/get_example, then always run validate_usage and validate_page. See get_design_rules for page standards.'
|
|
785
|
+
: '镜像此结构并替换业务数据。不熟悉的 API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。页面标准见 get_design_rules。',
|
|
739
786
|
});
|
|
740
787
|
}
|
|
741
788
|
function listGoldenPageCatalog(args = {}) {
|
|
@@ -769,8 +816,9 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
769
816
|
const ranked = pageSnippets
|
|
770
817
|
.map((snippet) => ({ snippet, score: scorePageSnippet(snippet, args.section) }))
|
|
771
818
|
.sort((a, b) => b.score - a.score || a.snippet.id.localeCompare(b.snippet.id));
|
|
772
|
-
const
|
|
773
|
-
|
|
819
|
+
const exact = findPageSnippet(args.section);
|
|
820
|
+
const match = exact || (ranked[0]?.score ? ranked[0].snippet : undefined);
|
|
821
|
+
if (!match || (!exact && (ranked[0]?.score ?? 0) === 0)) {
|
|
774
822
|
return textResult({
|
|
775
823
|
error: `Page snippet not found: ${args.section}`,
|
|
776
824
|
availableSnippets: pageSnippets.map((item) => item.id),
|
|
@@ -802,7 +850,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
802
850
|
template: match.template,
|
|
803
851
|
};
|
|
804
852
|
}
|
|
805
|
-
if (!
|
|
853
|
+
if (!exact && ranked.length > 1 && ranked[0]?.score) {
|
|
806
854
|
payload.matchedBy = 'query';
|
|
807
855
|
payload.alternatives = ranked
|
|
808
856
|
.slice(1, 4)
|
|
@@ -950,6 +998,61 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
950
998
|
});
|
|
951
999
|
}
|
|
952
1000
|
}
|
|
1001
|
+
if (/<MTable\b[^>]*(?::data|v-bind:data)\s*=/i.test(code) || /<MTable\b[^>]*\sdata\s*=/i.test(code)) {
|
|
1002
|
+
suggestions.push({
|
|
1003
|
+
standardId: 'feedback',
|
|
1004
|
+
type: 'table-data-prop',
|
|
1005
|
+
message: locale === 'en-US'
|
|
1006
|
+
? 'Contract: MTable row data uses the rows prop (not data). See golden list-page / get_component Table.'
|
|
1007
|
+
: '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。',
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
if (/<MMessage\b[^>]*\bseverity\b/i.test(code)) {
|
|
1011
|
+
suggestions.push({
|
|
1012
|
+
standardId: 'feedback',
|
|
1013
|
+
type: 'mmessage-as-alert',
|
|
1014
|
+
message: locale === 'en-US'
|
|
1015
|
+
? 'Contract: <MMessage> is the message service host, not an inline alert. Use field errorMessage or a token-styled role="alert".'
|
|
1016
|
+
: '契约:<MMessage> 是 message 服务宿主,不是内嵌 Alert。表单常驻错误用字段 errorMessage 或 token 样式的 role="alert"。',
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
if (/toast\.(?:success|info|warn|warning|error)\s*\(\s*['"`][^'"`]+['"`]\s*\)/i.test(code)) {
|
|
1020
|
+
suggestions.push({
|
|
1021
|
+
standardId: 'feedback',
|
|
1022
|
+
type: 'toast-one-liner',
|
|
1023
|
+
message: locale === 'en-US'
|
|
1024
|
+
? 'Contract: one-line feedback should use message.*; toast is for summary + detail or async feel.'
|
|
1025
|
+
: '契约:单行操作回执用 message.*;toast 仅用于 summary + detail 或异步通知感。',
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
if (/#cell-status[\s\S]{0,500}<MTag\b/i.test(code) &&
|
|
1029
|
+
!/#cell-status[\s\S]{0,500}<MStatus\b/i.test(code)) {
|
|
1030
|
+
suggestions.push({
|
|
1031
|
+
standardId: 'feedback',
|
|
1032
|
+
type: 'status-cell-tag',
|
|
1033
|
+
message: locale === 'en-US'
|
|
1034
|
+
? 'Prefer MStatus for business status in #cell-status; use MTag for categories / closable chips (see list-status-dot / detail-page).'
|
|
1035
|
+
: '行内业务状态优先 MStatus(#cell-status);分类或可关闭标签再用 MTag(见 list-status-dot / detail-page)。',
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
if (/<MTabs\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MTabs\b[^>]*\sitems\s*=/i.test(code)) {
|
|
1039
|
+
suggestions.push({
|
|
1040
|
+
standardId: 'feedback',
|
|
1041
|
+
type: 'tabs-items-prop',
|
|
1042
|
+
message: locale === 'en-US'
|
|
1043
|
+
? 'Contract: MTabs uses the tabs prop with v-model (not items / value). See settings-page golden.'
|
|
1044
|
+
: '契约:MTabs 使用 v-model + tabs(不是 items / value)。见黄金样例 settings-page。',
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
if (/<MStepper\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MStepper\b[^>]*\sitems\s*=/i.test(code)) {
|
|
1048
|
+
suggestions.push({
|
|
1049
|
+
standardId: 'feedback',
|
|
1050
|
+
type: 'stepper-items-prop',
|
|
1051
|
+
message: locale === 'en-US'
|
|
1052
|
+
? 'Contract: MStepper uses the steps prop with v-model (not items). See wizard-form golden.'
|
|
1053
|
+
: '契约:MStepper 使用 v-model + steps(不是 items)。见黄金样例 wizard-form。',
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
953
1056
|
return textResult({
|
|
954
1057
|
ok: true,
|
|
955
1058
|
advisory: true,
|
|
@@ -994,7 +1097,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
994
1097
|
spacing: '--m-space-*',
|
|
995
1098
|
radius: '--m-radius-sm/md/lg',
|
|
996
1099
|
typography: '--m-font-size-xs/sm/md/lg',
|
|
997
|
-
motion: '--m-motion-fast/normal',
|
|
1100
|
+
motion: '--m-motion-fast/normal/enter/exit/distance/ease; intensity via useMotion (data-m-motion); enter/exit via motion.transitions / transition prop (fade, scale-fade, zoom, slide-*, dialog, drawer, …)',
|
|
998
1101
|
},
|
|
999
1102
|
composition: designRules.composition,
|
|
1000
1103
|
actions: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morya-ui/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
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",
|