@loom-framework/core 0.1.0-alpha.8 → 0.1.0-alpha.81
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/dist/adapter-base.d.ts +29 -0
- package/dist/adapter-base.d.ts.map +1 -0
- package/dist/adapter-base.js +62 -0
- package/dist/adapter-base.js.map +1 -0
- package/dist/adapter-factory.d.ts +8 -0
- package/dist/adapter-factory.d.ts.map +1 -0
- package/dist/adapter-factory.js +25 -0
- package/dist/adapter-factory.js.map +1 -0
- package/dist/adapter-filesystem.d.ts +6 -11
- package/dist/adapter-filesystem.d.ts.map +1 -1
- package/dist/adapter-filesystem.js +56 -41
- package/dist/adapter-filesystem.js.map +1 -1
- package/dist/adapter-sqlite.d.ts +6 -23
- package/dist/adapter-sqlite.d.ts.map +1 -1
- package/dist/adapter-sqlite.js +65 -50
- package/dist/adapter-sqlite.js.map +1 -1
- package/dist/backend/ai/button-resolver.d.ts +18 -0
- package/dist/backend/ai/button-resolver.d.ts.map +1 -0
- package/dist/backend/ai/button-resolver.js +58 -0
- package/dist/backend/ai/button-resolver.js.map +1 -0
- package/dist/backend/ai/engine.d.ts +52 -0
- package/dist/backend/ai/engine.d.ts.map +1 -0
- package/dist/backend/ai/engine.js +186 -0
- package/dist/backend/ai/engine.js.map +1 -0
- package/dist/backend/ai/index.d.ts +11 -0
- package/dist/backend/ai/index.d.ts.map +1 -0
- package/dist/backend/ai/index.js +8 -0
- package/dist/backend/ai/index.js.map +1 -0
- package/dist/backend/ai/output-parser.d.ts +29 -0
- package/dist/backend/ai/output-parser.d.ts.map +1 -0
- package/dist/backend/ai/output-parser.js +247 -0
- package/dist/backend/ai/output-parser.js.map +1 -0
- package/dist/backend/ai/session-manager.d.ts +103 -0
- package/dist/backend/ai/session-manager.d.ts.map +1 -0
- package/dist/backend/ai/session-manager.js +298 -0
- package/dist/backend/ai/session-manager.js.map +1 -0
- package/dist/backend/index.d.ts +61 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +161 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/observe/index.d.ts +6 -0
- package/dist/backend/observe/index.d.ts.map +1 -0
- package/dist/backend/observe/index.js +5 -0
- package/dist/backend/observe/index.js.map +1 -0
- package/dist/backend/observe/logger.d.ts +28 -0
- package/dist/backend/observe/logger.d.ts.map +1 -0
- package/dist/backend/observe/logger.js +80 -0
- package/dist/backend/observe/logger.js.map +1 -0
- package/dist/backend/observe/types.d.ts +26 -0
- package/dist/backend/observe/types.d.ts.map +1 -0
- package/dist/backend/observe/types.js +7 -0
- package/dist/backend/observe/types.js.map +1 -0
- package/dist/backend/routes/chat.d.ts +31 -0
- package/dist/backend/routes/chat.d.ts.map +1 -0
- package/dist/backend/routes/chat.js +426 -0
- package/dist/backend/routes/chat.js.map +1 -0
- package/dist/backend/routes/data.d.ts +13 -0
- package/dist/backend/routes/data.d.ts.map +1 -0
- package/dist/backend/routes/data.js +134 -0
- package/dist/backend/routes/data.js.map +1 -0
- package/dist/backend/routes/health.d.ts +7 -0
- package/dist/backend/routes/health.d.ts.map +1 -0
- package/dist/backend/routes/health.js +15 -0
- package/dist/backend/routes/health.js.map +1 -0
- package/dist/backend/routes/index.d.ts +11 -0
- package/dist/backend/routes/index.d.ts.map +1 -0
- package/dist/backend/routes/index.js +9 -0
- package/dist/backend/routes/index.js.map +1 -0
- package/dist/backend/routes/skills.d.ts +16 -0
- package/dist/backend/routes/skills.d.ts.map +1 -0
- package/dist/backend/routes/skills.js +590 -0
- package/dist/backend/routes/skills.js.map +1 -0
- package/dist/backend/routes/upload.d.ts +24 -0
- package/dist/backend/routes/upload.d.ts.map +1 -0
- package/dist/backend/routes/upload.js +67 -0
- package/dist/backend/routes/upload.js.map +1 -0
- package/dist/bin.d.ts +8 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +12 -0
- package/dist/bin.js.map +1 -0
- package/dist/capability-generator.d.ts +21 -6
- package/dist/capability-generator.d.ts.map +1 -1
- package/dist/capability-generator.js +88 -261
- package/dist/capability-generator.js.map +1 -1
- package/dist/cli/commands/build.d.ts +11 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +170 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/data.d.ts +12 -0
- package/dist/cli/commands/data.d.ts.map +1 -0
- package/dist/cli/commands/data.js +158 -0
- package/dist/cli/commands/data.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +9 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +114 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/generate-capabilities.d.ts +8 -0
- package/dist/cli/commands/generate-capabilities.d.ts.map +1 -0
- package/dist/cli/commands/generate-capabilities.js +40 -0
- package/dist/cli/commands/generate-capabilities.js.map +1 -0
- package/dist/cli/commands/generate-cli-command.d.ts +8 -0
- package/dist/cli/commands/generate-cli-command.d.ts.map +1 -0
- package/dist/cli/commands/generate-cli-command.js +64 -0
- package/dist/cli/commands/generate-cli-command.js.map +1 -0
- package/dist/cli/commands/generate-dashboard.d.ts +9 -0
- package/dist/cli/commands/generate-dashboard.d.ts.map +1 -0
- package/dist/cli/commands/generate-dashboard.js +452 -0
- package/dist/cli/commands/generate-dashboard.js.map +1 -0
- package/dist/cli/commands/generate-page.d.ts +9 -0
- package/dist/cli/commands/generate-page.d.ts.map +1 -0
- package/dist/cli/commands/generate-page.js +518 -0
- package/dist/cli/commands/generate-page.js.map +1 -0
- package/dist/cli/commands/generate-skill.d.ts +8 -0
- package/dist/cli/commands/generate-skill.d.ts.map +1 -0
- package/dist/cli/commands/generate-skill.js +75 -0
- package/dist/cli/commands/generate-skill.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +6 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +19 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +539 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/observe.d.ts +9 -0
- package/dist/cli/commands/observe.d.ts.map +1 -0
- package/dist/cli/commands/observe.js +142 -0
- package/dist/cli/commands/observe.js.map +1 -0
- package/dist/cli/commands/skill.d.ts +9 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +186 -0
- package/dist/cli/commands/skill.js.map +1 -0
- package/dist/cli/helpers/app-tsx-wiring.d.ts +17 -0
- package/dist/cli/helpers/app-tsx-wiring.d.ts.map +1 -0
- package/dist/cli/helpers/app-tsx-wiring.js +132 -0
- package/dist/cli/helpers/app-tsx-wiring.js.map +1 -0
- package/dist/cli/helpers/duration.d.ts +5 -0
- package/dist/cli/helpers/duration.d.ts.map +1 -0
- package/dist/cli/helpers/duration.js +19 -0
- package/dist/cli/helpers/duration.js.map +1 -0
- package/dist/cli/helpers/field-template.d.ts +10 -0
- package/dist/cli/helpers/field-template.d.ts.map +1 -0
- package/dist/cli/helpers/field-template.js +100 -0
- package/dist/cli/helpers/field-template.js.map +1 -0
- package/dist/cli/helpers/naming.d.ts +12 -0
- package/dist/cli/helpers/naming.d.ts.map +1 -0
- package/dist/cli/helpers/naming.js +25 -0
- package/dist/cli/helpers/naming.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +33 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils.d.ts +10 -0
- package/dist/cli/utils.d.ts.map +1 -0
- package/dist/cli/utils.js +31 -0
- package/dist/cli/utils.js.map +1 -0
- package/dist/config.d.ts +118 -42
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -10
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/server-bin.d.ts +12 -0
- package/dist/server-bin.d.ts.map +1 -0
- package/dist/server-bin.js +75 -0
- package/dist/server-bin.js.map +1 -0
- package/dist/types.d.ts +71 -20
- package/dist/types.d.ts.map +1 -1
- package/package.json +25 -10
- package/templates/app-skill/SKILL.md +27 -0
- package/templates/app-skill/references/data-semantics.md +44 -0
- package/templates/app-skill/references/models.md +31 -0
- package/templates/loom-skill/SKILL.md +153 -0
- package/templates/loom-skill/references/README.md +128 -0
- package/templates/loom-skill/references/dashboard.md +161 -0
- package/templates/loom-skill/references/data-model.md +78 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loom generate page <name>
|
|
3
|
+
*
|
|
4
|
+
* With --model: generate capabilities → CRUD page → auto-wire App.tsx
|
|
5
|
+
* Without --model: generate minimal skeleton page
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { loadConfig, getModelSchema, generateCapabilities } from '../../index.js';
|
|
11
|
+
import { resolveProjectRoot } from '../utils.js';
|
|
12
|
+
import { toPascalCase } from '../helpers/naming.js';
|
|
13
|
+
import { fieldToFormItem } from '../helpers/field-template.js';
|
|
14
|
+
import { wireAppTsxAutomatic, wireSkillManagementPage } from '../helpers/app-tsx-wiring.js';
|
|
15
|
+
/** Semantic color mapping for common enum values */
|
|
16
|
+
const SEMANTIC_COLORS = {
|
|
17
|
+
'active': 'green', 'inactive': 'default', 'pending': 'orange',
|
|
18
|
+
'enabled': 'green', 'disabled': 'default',
|
|
19
|
+
'未掌握': 'red', '部分掌握': 'orange', '已掌握': 'green',
|
|
20
|
+
'仍需练习': 'orange',
|
|
21
|
+
'简单': 'green', '中等': 'blue', '困难': 'red',
|
|
22
|
+
'easy': 'green', 'medium': 'blue', 'hard': 'red',
|
|
23
|
+
'high': 'red', 'low': 'green',
|
|
24
|
+
'是': 'green', '否': 'red',
|
|
25
|
+
'yes': 'green', 'no': 'red',
|
|
26
|
+
};
|
|
27
|
+
const TAG_PALETTE = ['blue', 'green', 'orange', 'red', 'purple', 'cyan', 'magenta', 'volcano', 'gold', 'lime'];
|
|
28
|
+
/** Fallback labels for common field names without description */
|
|
29
|
+
const FALLBACK_LABELS = {
|
|
30
|
+
createdAt: '创建时间',
|
|
31
|
+
updatedAt: '更新时间',
|
|
32
|
+
};
|
|
33
|
+
/** Generate tagColors constant code for enum fields */
|
|
34
|
+
function generateTagColors(fields) {
|
|
35
|
+
const enumFields = fields.filter(f => f.enum && f.name !== 'id');
|
|
36
|
+
if (enumFields.length === 0)
|
|
37
|
+
return '';
|
|
38
|
+
const entries = enumFields.map(f => {
|
|
39
|
+
const mappings = f.enum.map((v, i) => {
|
|
40
|
+
const color = SEMANTIC_COLORS[v] || TAG_PALETTE[i % TAG_PALETTE.length];
|
|
41
|
+
return `'${v}': '${color}'`;
|
|
42
|
+
}).join(', ');
|
|
43
|
+
return ` ${f.name}: { ${mappings} }`;
|
|
44
|
+
}).join(',\n');
|
|
45
|
+
return `\nconst tagColors: Record<string, Record<string, string>> = {\n${entries}\n};\n`;
|
|
46
|
+
}
|
|
47
|
+
/** Check if a string field is likely a long-text field */
|
|
48
|
+
function isLongTextField(field) {
|
|
49
|
+
const name = field.name.toLowerCase();
|
|
50
|
+
return (field.type === 'string' &&
|
|
51
|
+
!field.enum &&
|
|
52
|
+
(name.includes('content') ||
|
|
53
|
+
name.includes('description') ||
|
|
54
|
+
name.includes('body') ||
|
|
55
|
+
name.includes('text') ||
|
|
56
|
+
name.includes('note') ||
|
|
57
|
+
name.includes('remark') ||
|
|
58
|
+
name.includes('bio') ||
|
|
59
|
+
name.includes('summary') ||
|
|
60
|
+
name.includes('analysis')));
|
|
61
|
+
}
|
|
62
|
+
/** Get column width based on field type */
|
|
63
|
+
function getColumnWidth(field) {
|
|
64
|
+
if (isLongTextField(field))
|
|
65
|
+
return 300;
|
|
66
|
+
if (field.enum)
|
|
67
|
+
return 110;
|
|
68
|
+
if (field.type === 'boolean')
|
|
69
|
+
return 90;
|
|
70
|
+
if (field.type === 'number')
|
|
71
|
+
return 100;
|
|
72
|
+
if (field.type === 'date')
|
|
73
|
+
return 130;
|
|
74
|
+
if (field.type === 'string[]')
|
|
75
|
+
return 180;
|
|
76
|
+
if (field.type === 'number[]')
|
|
77
|
+
return 140;
|
|
78
|
+
if (field.type === 'json')
|
|
79
|
+
return 200;
|
|
80
|
+
return 160; // default string
|
|
81
|
+
}
|
|
82
|
+
/** Generate table columns from model fields — server-side filter & sort */
|
|
83
|
+
function generateColumns(fields, modelName, aiButtons) {
|
|
84
|
+
const cols = fields
|
|
85
|
+
.filter((f) => f.name !== 'id') // skip id column in form
|
|
86
|
+
.map((f) => {
|
|
87
|
+
const label = f.description || FALLBACK_LABELS[f.name] || f.name;
|
|
88
|
+
const width = getColumnWidth(f);
|
|
89
|
+
const extras = [` width: ${width},`];
|
|
90
|
+
const renders = [];
|
|
91
|
+
if (f.type === 'boolean') {
|
|
92
|
+
renders.push(` render: (val: boolean) => <Tag color={val ? 'green' : 'red'}>{val ? '是' : '否'}</Tag>,`);
|
|
93
|
+
}
|
|
94
|
+
else if (f.enum) {
|
|
95
|
+
// Enum columns: Tag render + server-side column filter
|
|
96
|
+
renders.push(` render: (val: string) => val ? <Tag color={tagColors?.['${f.name}']?.[val] || 'blue'}>{val}</Tag> : '-',`);
|
|
97
|
+
const filterItems = f.enum.map(v => ` { text: '${v}', value: '${v}' },`).join('\n');
|
|
98
|
+
extras.push(` filters: [\n${filterItems}\n ],`);
|
|
99
|
+
extras.push(` filteredValue: tableFilters.${f.name} || null,`);
|
|
100
|
+
}
|
|
101
|
+
else if (f.type === 'string[]') {
|
|
102
|
+
renders.push(` render: (val: string[]) => val?.map((v, i) => <Tag key={i}>{v}</Tag>),`);
|
|
103
|
+
}
|
|
104
|
+
else if (f.type === 'number[]') {
|
|
105
|
+
renders.push(` render: (val: number[]) => val?.join(', '),`);
|
|
106
|
+
}
|
|
107
|
+
else if (f.type === 'number') {
|
|
108
|
+
// Number columns: server-side sortable
|
|
109
|
+
extras.push(` sorter: true,`);
|
|
110
|
+
extras.push(` sortOrder: tableSorter.field === '${f.name}' ? tableSorter.order : null,`);
|
|
111
|
+
}
|
|
112
|
+
else if (f.type === 'date') {
|
|
113
|
+
// Date columns: server-side range filter
|
|
114
|
+
extras.push(` filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }: any) => (
|
|
115
|
+
<div style={{ padding: 8 }}>
|
|
116
|
+
<DatePicker.RangePicker
|
|
117
|
+
style={{ marginBottom: 8, display: 'block' }}
|
|
118
|
+
value={selectedKeys[0] ? selectedKeys : []}
|
|
119
|
+
onChange={(dates) => {
|
|
120
|
+
setSelectedKeys(dates ? [dates] : []);
|
|
121
|
+
confirm({ closeDropdown: false });
|
|
122
|
+
}}
|
|
123
|
+
/>
|
|
124
|
+
<Space>
|
|
125
|
+
<Button type="primary" size="small" onClick={() => confirm()}>筛选</Button>
|
|
126
|
+
<Button size="small" onClick={() => { clearFilters?.(); confirm(); }}>重置</Button>
|
|
127
|
+
</Space>
|
|
128
|
+
</div>
|
|
129
|
+
),`);
|
|
130
|
+
extras.push(` filteredValue: tableFilters.${f.name} || null,`);
|
|
131
|
+
}
|
|
132
|
+
else if (isLongTextField(f)) {
|
|
133
|
+
// Long text: auto wrap + 2-line ellipsis with expandable
|
|
134
|
+
extras.push(` onCell: () => ({ style: { wordBreak: 'break-word' as const } }),`);
|
|
135
|
+
renders.push(` render: (val: string) => val ? <Typography.Paragraph ellipsis={{ rows: 2, expandable: 'collapsible', symbol: '展开' }} style={{ marginBottom: 0 }}>{val}</Typography.Paragraph> : '-',`);
|
|
136
|
+
}
|
|
137
|
+
else if (f.type === 'string' && !f.enum) {
|
|
138
|
+
// Short string: tooltip ellipsis
|
|
139
|
+
extras.push(` ellipsis: true,`);
|
|
140
|
+
}
|
|
141
|
+
return ` {
|
|
142
|
+
title: '${label}',
|
|
143
|
+
dataIndex: '${f.name}',
|
|
144
|
+
key: '${f.name}',
|
|
145
|
+
${extras.join('\n')}${renders.join('\n')} },`;
|
|
146
|
+
})
|
|
147
|
+
.join('\n');
|
|
148
|
+
// Add action column
|
|
149
|
+
const aiButtonMenuCode = aiButtons && aiButtons.length > 0
|
|
150
|
+
? `<Tooltip title="AI 助手"><Dropdown menu={{ items: aiButtonMenuItems(record) }}>
|
|
151
|
+
<Button type="text" size="small" icon={<ThunderboltOutlined />} />
|
|
152
|
+
</Dropdown></Tooltip>`
|
|
153
|
+
: '';
|
|
154
|
+
const actionButtons = [
|
|
155
|
+
`<Tooltip title="编辑"><Button type="text" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)} /></Tooltip>`,
|
|
156
|
+
aiButtonMenuCode,
|
|
157
|
+
`<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
|
|
158
|
+
<Tooltip title="删除"><Button type="text" size="small" danger icon={<DeleteOutlined />} /></Tooltip>
|
|
159
|
+
</Popconfirm>`,
|
|
160
|
+
].filter(Boolean).join('\n ');
|
|
161
|
+
return `${cols}
|
|
162
|
+
{
|
|
163
|
+
title: '操作',
|
|
164
|
+
key: 'action',
|
|
165
|
+
width: ${aiButtons && aiButtons.length > 0 ? 120 : 90},
|
|
166
|
+
fixed: 'right' as const,
|
|
167
|
+
render: (_: unknown, record: ${toPascalCase(modelName)}Record) => (
|
|
168
|
+
<Space>
|
|
169
|
+
${actionButtons}
|
|
170
|
+
</Space>
|
|
171
|
+
),
|
|
172
|
+
},`;
|
|
173
|
+
}
|
|
174
|
+
/** Build display label from model description */
|
|
175
|
+
function buildLabel(description, pascalName) {
|
|
176
|
+
return description || pascalName;
|
|
177
|
+
}
|
|
178
|
+
/** Generate CRUD page template from model schema */
|
|
179
|
+
function crudPageTemplate(model, aiButtons) {
|
|
180
|
+
const pascalName = toPascalCase(model.name);
|
|
181
|
+
const recordType = `${pascalName}Record`;
|
|
182
|
+
// Detect field types for conditional logic
|
|
183
|
+
const dateFields = model.fields.filter(f => f.type === 'date').map(f => f.name);
|
|
184
|
+
const hasDateFields = dateFields.length > 0;
|
|
185
|
+
// Build TypeScript interface from fields
|
|
186
|
+
const fieldDefs = model.fields.map((f) => {
|
|
187
|
+
const tsType = f.type === 'string[]' ? 'string[]'
|
|
188
|
+
: f.type === 'number[]' ? 'number[]'
|
|
189
|
+
: f.type === 'json' ? 'Record<string, unknown>'
|
|
190
|
+
: f.type === 'number' ? 'number'
|
|
191
|
+
: f.type === 'boolean' ? 'boolean'
|
|
192
|
+
: f.type === 'date' ? 'string'
|
|
193
|
+
: 'string';
|
|
194
|
+
return ` ${f.name}${f.required ? '' : '?'}: ${tsType};`;
|
|
195
|
+
}).join('\n');
|
|
196
|
+
const columns = generateColumns(model.fields, model.name, aiButtons);
|
|
197
|
+
const formItems = model.fields
|
|
198
|
+
.filter((f) => f.name !== 'id') // id is auto-generated
|
|
199
|
+
.map((f) => fieldToFormItem(f, model.name))
|
|
200
|
+
.join('\n');
|
|
201
|
+
// Generate handleEdit with date conversion
|
|
202
|
+
const handleEditBody = hasDateFields
|
|
203
|
+
? `const handleEdit = (record: ${recordType}) => {
|
|
204
|
+
setEditingRecord(record);
|
|
205
|
+
const formValues = { ...record };
|
|
206
|
+
${dateFields.map(f => ` if (formValues.${f}) formValues.${f} = dayjs(formValues.${f}) as any;`).join('\n')}
|
|
207
|
+
form.setFieldsValue(formValues);
|
|
208
|
+
setModalOpen(true);
|
|
209
|
+
};`
|
|
210
|
+
: `const handleEdit = (record: ${recordType}) => {
|
|
211
|
+
setEditingRecord(record);
|
|
212
|
+
form.setFieldsValue(record);
|
|
213
|
+
setModalOpen(true);
|
|
214
|
+
};`;
|
|
215
|
+
// Generate handleSave with date serialization
|
|
216
|
+
const handleSaveBody = hasDateFields
|
|
217
|
+
? `const handleSave = async () => {
|
|
218
|
+
try {
|
|
219
|
+
const values = await form.validateFields();
|
|
220
|
+
${dateFields.map(f => ` if (values.${f}) values.${f} = values.${f}.format('YYYY-MM-DD');`).join('\n')}
|
|
221
|
+
if (editingRecord?.id) {
|
|
222
|
+
await update(editingRecord.id, values);
|
|
223
|
+
message.success('更新成功');
|
|
224
|
+
} else {
|
|
225
|
+
await create(values);
|
|
226
|
+
message.success('创建成功');
|
|
227
|
+
}
|
|
228
|
+
setModalOpen(false);
|
|
229
|
+
fetchData();
|
|
230
|
+
} catch {
|
|
231
|
+
// form validation failed
|
|
232
|
+
}
|
|
233
|
+
};`
|
|
234
|
+
: `const handleSave = async () => {
|
|
235
|
+
try {
|
|
236
|
+
const values = await form.validateFields();
|
|
237
|
+
if (editingRecord?.id) {
|
|
238
|
+
await update(editingRecord.id, values);
|
|
239
|
+
message.success('更新成功');
|
|
240
|
+
} else {
|
|
241
|
+
await create(values);
|
|
242
|
+
message.success('创建成功');
|
|
243
|
+
}
|
|
244
|
+
setModalOpen(false);
|
|
245
|
+
fetchData();
|
|
246
|
+
} catch {
|
|
247
|
+
// form validation failed
|
|
248
|
+
}
|
|
249
|
+
};`;
|
|
250
|
+
const dayjsImport = hasDateFields ? `\nimport dayjs from 'dayjs';` : '';
|
|
251
|
+
const hasAIButtons = aiButtons && aiButtons.length > 0;
|
|
252
|
+
// Build imports — merge AI-specific imports into existing lines
|
|
253
|
+
const antdExtra = hasAIButtons ? ', Dropdown' : '';
|
|
254
|
+
const iconsExtra = hasAIButtons ? ', ThunderboltOutlined' : '';
|
|
255
|
+
const extraIcons = ', EditOutlined, DeleteOutlined';
|
|
256
|
+
const loomExtra = hasAIButtons ? ', AIContext' : '';
|
|
257
|
+
const reactExtra = hasAIButtons ? ', useContext' : '';
|
|
258
|
+
return `import React, { useEffect, useState, useCallback${reactExtra} } from 'react';
|
|
259
|
+
import { Table, Button, Modal, Form, Input, InputNumber, Select, Switch, DatePicker, Space, Popconfirm, message, Tag, Card, Flex, Tooltip, Typography, type TableProps${antdExtra} } from 'antd';
|
|
260
|
+
import { PlusOutlined${iconsExtra}${extraIcons} } from '@ant-design/icons';
|
|
261
|
+
import { useData${loomExtra} } from '@loom-framework/frontend-antd';${dayjsImport}
|
|
262
|
+
|
|
263
|
+
interface ${recordType} {
|
|
264
|
+
id: string;
|
|
265
|
+
${fieldDefs}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const modelFields: { name: string; type: string }[] = [
|
|
269
|
+
${model.fields.map(f => ` { name: '${f.name}', type: '${f.type}' },`).join('\n')}
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
export function ${pascalName}Page(): React.ReactElement {
|
|
273
|
+
const { list, create, update, remove, loading, refresh } = useData<${recordType}>('${model.name}');
|
|
274
|
+
const [form] = Form.useForm();
|
|
275
|
+
const [modalOpen, setModalOpen] = useState(false);
|
|
276
|
+
const [editingRecord, setEditingRecord] = useState<${recordType} | null>(null);
|
|
277
|
+
const [tableFilters, setTableFilters] = useState<Record<string, any>>({});
|
|
278
|
+
const [tableSorter, setTableSorter] = useState<{ field: string; order: 'ascend' | 'descend' | null }>({ field: '', order: null });
|
|
279
|
+
|
|
280
|
+
const fetchData = useCallback((filters?: Record<string, (string | number | boolean | null)[] | null>, sorter?: { field: string; order: 'ascend' | 'descend' | null }) => {
|
|
281
|
+
const currentFilters = filters ?? tableFilters;
|
|
282
|
+
const currentSorter = sorter ?? tableSorter;
|
|
283
|
+
const queryParams: any = {};
|
|
284
|
+
// Build filter from antd column filter values
|
|
285
|
+
const apiFilter: Record<string, unknown> = {};
|
|
286
|
+
for (const [key, val] of Object.entries(currentFilters)) {
|
|
287
|
+
if (val && val.length > 0) {
|
|
288
|
+
const field = modelFields.find(f => f.name === key);
|
|
289
|
+
if (field?.type === 'date') {
|
|
290
|
+
// Date range: val is [dayjs, dayjs] stored in filter
|
|
291
|
+
apiFilter[key] = { __range: val.map((v: any) => v?.format?.('YYYY-MM-DD') || v) };
|
|
292
|
+
} else if (val.length === 1) {
|
|
293
|
+
apiFilter[key] = val[0];
|
|
294
|
+
} else {
|
|
295
|
+
// Multiple filter values for a single column (e.g. subject in [数学, 英语])
|
|
296
|
+
apiFilter[key] = val;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (Object.keys(apiFilter).length > 0) queryParams.filter = apiFilter;
|
|
301
|
+
// Build sort
|
|
302
|
+
if (currentSorter.field && currentSorter.order) {
|
|
303
|
+
queryParams.sort = currentSorter.field;
|
|
304
|
+
queryParams.sortOrder = currentSorter.order === 'descend' ? 'desc' : 'asc';
|
|
305
|
+
}
|
|
306
|
+
refresh(queryParams);
|
|
307
|
+
}, [refresh, tableFilters, tableSorter]);
|
|
308
|
+
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
fetchData({}, { field: '', order: null });
|
|
311
|
+
}, []);
|
|
312
|
+
|
|
313
|
+
const handleAdd = () => {
|
|
314
|
+
setEditingRecord(null);
|
|
315
|
+
form.resetFields();
|
|
316
|
+
setModalOpen(true);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
${handleEditBody}
|
|
320
|
+
|
|
321
|
+
const handleDelete = async (id: string) => {
|
|
322
|
+
try {
|
|
323
|
+
await remove(id);
|
|
324
|
+
message.success('删除成功');
|
|
325
|
+
fetchData();
|
|
326
|
+
} catch {
|
|
327
|
+
message.error('删除失败');
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
${handleSaveBody}
|
|
332
|
+
${hasAIButtons ? `
|
|
333
|
+
const ai = useContext(AIContext);
|
|
334
|
+
|
|
335
|
+
// Build context object from the current record for AI prompts
|
|
336
|
+
// Key = raw field name (matches {{var}} in prompt templates)
|
|
337
|
+
// Value = description + value for fields with descriptions
|
|
338
|
+
const buildRecordContext = (record: ${recordType}): Record<string, string | string[] | number | boolean | null | undefined> => {
|
|
339
|
+
const labelMap: Record<string, string> = {
|
|
340
|
+
${model.fields.map(f => ` ${f.name}: '${f.description || ''}',`).join('\n')}
|
|
341
|
+
};
|
|
342
|
+
const ctx: Record<string, string | string[] | number | boolean | null | undefined> = {};
|
|
343
|
+
for (const [k, v] of Object.entries(record)) {
|
|
344
|
+
if (v != null && v !== '' && !(Array.isArray(v) && v.length === 0)) {
|
|
345
|
+
ctx[k] = v as string | string[] | number | boolean | null | undefined;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return ctx;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const aiButtonMenuItems = (record: ${recordType}) => {
|
|
352
|
+
const ctx = buildRecordContext(record);
|
|
353
|
+
return [
|
|
354
|
+
${aiButtons.map(btn => {
|
|
355
|
+
return ` { key: '${btn.id}', label: '${btn.label}', onClick: () => ai?.triggerAI({ buttonId: '${btn.id}', label: '${btn.label}', prompt: '${btn.prompt.replace(/'/g, "\\'").replace(/\n/g, "\\n")}', context: ctx }) },`;
|
|
356
|
+
}).join('\n')}
|
|
357
|
+
];
|
|
358
|
+
};` : ''}
|
|
359
|
+
|
|
360
|
+
const columns: TableProps<${recordType}>['columns'] = [
|
|
361
|
+
${columns}
|
|
362
|
+
];
|
|
363
|
+
${generateTagColors(model.fields)}
|
|
364
|
+
const handleTableChange: TableProps<${recordType}>['onChange'] = (pagination, filters, sorter) => {
|
|
365
|
+
const newFilters = filters as Record<string, any>;
|
|
366
|
+
const newSorter = !Array.isArray(sorter) && sorter.field
|
|
367
|
+
? { field: sorter.field as string, order: sorter.order as 'ascend' | 'descend' | null }
|
|
368
|
+
: { field: '', order: null };
|
|
369
|
+
setTableFilters(newFilters);
|
|
370
|
+
setTableSorter(newSorter);
|
|
371
|
+
fetchData(newFilters, newSorter);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
<>
|
|
376
|
+
<Card>
|
|
377
|
+
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
|
378
|
+
<h2 style={{ margin: 0 }}>${buildLabel(model.description, pascalName)}</h2>
|
|
379
|
+
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
|
|
380
|
+
新增
|
|
381
|
+
</Button>
|
|
382
|
+
</Flex>
|
|
383
|
+
|
|
384
|
+
<Table
|
|
385
|
+
columns={columns}
|
|
386
|
+
dataSource={list}
|
|
387
|
+
rowKey="id"
|
|
388
|
+
loading={loading}
|
|
389
|
+
size="middle"
|
|
390
|
+
scroll={{ x: 'max-content' }}
|
|
391
|
+
onChange={handleTableChange}
|
|
392
|
+
pagination={{ pageSize: 10, showSizeChanger: true, showTotal: (total) => \`共 \${total} 条\` }}
|
|
393
|
+
/>
|
|
394
|
+
</Card>
|
|
395
|
+
|
|
396
|
+
<Modal
|
|
397
|
+
title={editingRecord ? '编辑${buildLabel(model.description, pascalName)}' : '新增${buildLabel(model.description, pascalName)}'}
|
|
398
|
+
open={modalOpen}
|
|
399
|
+
onOk={handleSave}
|
|
400
|
+
onCancel={() => setModalOpen(false)}
|
|
401
|
+
destroyOnHidden
|
|
402
|
+
width={640}
|
|
403
|
+
>
|
|
404
|
+
<Form form={form} layout="vertical">
|
|
405
|
+
${formItems}
|
|
406
|
+
</Form>
|
|
407
|
+
</Modal>
|
|
408
|
+
</>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export default ${pascalName}Page;
|
|
413
|
+
`;
|
|
414
|
+
}
|
|
415
|
+
/** Minimal skeleton page template (no model) */
|
|
416
|
+
const SKELETON_TEMPLATE = (pascalName) => `import React from 'react';
|
|
417
|
+
|
|
418
|
+
export interface ${pascalName}Props {
|
|
419
|
+
// TODO: Define page props
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function ${pascalName}(props: ${pascalName}Props): React.ReactElement {
|
|
423
|
+
return (
|
|
424
|
+
<div className="${pascalName.toLowerCase()}-page">
|
|
425
|
+
<h1>${pascalName}</h1>
|
|
426
|
+
<p>TODO: Implement this page.</p>
|
|
427
|
+
</div>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export default ${pascalName};
|
|
432
|
+
`;
|
|
433
|
+
export function registerGeneratePageCommand(program) {
|
|
434
|
+
program
|
|
435
|
+
.command('page <name>')
|
|
436
|
+
.description('Create a new frontend page component with PascalCase naming')
|
|
437
|
+
.option('--model <model>', 'Data model name from loom.config.ts to generate CRUD page (auto-generates capabilities and wires App.tsx)')
|
|
438
|
+
.action(async (name, cmdOptions) => {
|
|
439
|
+
try {
|
|
440
|
+
const projectRoot = await resolveProjectRoot();
|
|
441
|
+
const pascalName = toPascalCase(name);
|
|
442
|
+
const pageDir = path.join(projectRoot, 'frontend', 'src', 'components', 'pages');
|
|
443
|
+
const pagePath = path.join(pageDir, `${pascalName}.tsx`);
|
|
444
|
+
// Check if page already exists
|
|
445
|
+
try {
|
|
446
|
+
await fs.access(pagePath);
|
|
447
|
+
console.error(chalk.red(`Page "${pascalName}" already exists at ${pagePath}`));
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// File does not exist, proceed
|
|
452
|
+
}
|
|
453
|
+
let template;
|
|
454
|
+
let modelLabel = pascalName;
|
|
455
|
+
if (cmdOptions.model) {
|
|
456
|
+
// Load config and find model schema
|
|
457
|
+
const config = await loadConfig(projectRoot);
|
|
458
|
+
const modelSchema = getModelSchema(config, cmdOptions.model);
|
|
459
|
+
if (!modelSchema) {
|
|
460
|
+
console.error(chalk.red(`Model "${cmdOptions.model}" not found in loom.config.ts`));
|
|
461
|
+
console.error(chalk.dim(`Available models: ${config.data.models.map((m) => m.name).join(', ')}`));
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
modelLabel = buildLabel(modelSchema.description, pascalName);
|
|
465
|
+
// Step 1: Auto-generate capabilities (Skill for data access via CLI)
|
|
466
|
+
const capResult = await generateCapabilities(projectRoot, config);
|
|
467
|
+
console.log(chalk.green('Capabilities generated'), chalk.dim(`(${capResult.filesWritten.length} files)`));
|
|
468
|
+
// Step 2: Generate CRUD page — filter aiButtons by placement
|
|
469
|
+
const modelAiButtons = config.aiButtons?.filter(btn => {
|
|
470
|
+
// No placement = show on all pages (backward compatible)
|
|
471
|
+
if (!btn.placement)
|
|
472
|
+
return true;
|
|
473
|
+
// Comma-separated list of model names
|
|
474
|
+
const targets = btn.placement.split(',').map(s => s.trim());
|
|
475
|
+
return targets.includes(cmdOptions.model);
|
|
476
|
+
});
|
|
477
|
+
template = crudPageTemplate(modelSchema, modelAiButtons);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
template = SKELETON_TEMPLATE(pascalName);
|
|
481
|
+
}
|
|
482
|
+
await fs.mkdir(pageDir, { recursive: true });
|
|
483
|
+
await fs.writeFile(pagePath, template, 'utf-8');
|
|
484
|
+
console.log(chalk.green('Page created successfully!'));
|
|
485
|
+
console.log();
|
|
486
|
+
console.log(chalk.bold(' Page:'), pascalName);
|
|
487
|
+
console.log(chalk.bold(' Path:'), path.join('frontend', 'src', 'components', 'pages', `${pascalName}.tsx`));
|
|
488
|
+
if (cmdOptions.model) {
|
|
489
|
+
console.log(chalk.bold(' Model:'), cmdOptions.model);
|
|
490
|
+
}
|
|
491
|
+
// Step 3: Auto-wire App.tsx when using --model
|
|
492
|
+
if (cmdOptions.model) {
|
|
493
|
+
const wired = await wireAppTsxAutomatic(projectRoot, pascalName, cmdOptions.model, modelLabel);
|
|
494
|
+
if (wired) {
|
|
495
|
+
console.log(chalk.green(' App.tsx wired automatically'));
|
|
496
|
+
// Also wire SkillManagementPage on first page generation
|
|
497
|
+
const skillWired = await wireSkillManagementPage(projectRoot);
|
|
498
|
+
if (skillWired) {
|
|
499
|
+
console.log(chalk.green(' Skill management page wired'));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
console.log(chalk.yellow(' Could not auto-wire App.tsx — add the page import, navItem, and switch case manually'));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
console.log();
|
|
508
|
+
console.log(chalk.dim(' Use --model <name> to generate a CRUD page from a data model.'));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch (err) {
|
|
512
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
513
|
+
console.error(chalk.red('Failed to create page:'), message);
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
//# sourceMappingURL=generate-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-page.js","sourceRoot":"","sources":["../../../src/cli/commands/generate-page.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loom generate skill <name>
|
|
3
|
+
*
|
|
4
|
+
* Create .claude/skills/<name>/SKILL.md skeleton and references/ directory.
|
|
5
|
+
*/
|
|
6
|
+
import type { Command } from 'commander';
|
|
7
|
+
export declare function registerGenerateSkillCommand(program: Command): void;
|
|
8
|
+
//# sourceMappingURL=generate-skill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-skill.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate-skill.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCzC,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyCnE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loom generate skill <name>
|
|
3
|
+
*
|
|
4
|
+
* Create .claude/skills/<name>/SKILL.md skeleton and references/ directory.
|
|
5
|
+
*/
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { promises as fs } from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { resolveProjectRoot } from '../utils.js';
|
|
10
|
+
const SKILL_MD_TEMPLATE = (name) => `---
|
|
11
|
+
name: ${name}
|
|
12
|
+
version: 0.1.0
|
|
13
|
+
description: ""
|
|
14
|
+
tags: []
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# ${name}
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
TODO: Describe what this skill does.
|
|
22
|
+
|
|
23
|
+
## Instructions
|
|
24
|
+
|
|
25
|
+
TODO: Step-by-step instructions for the skill.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
TODO: Example inputs and outputs.
|
|
30
|
+
|
|
31
|
+
## References
|
|
32
|
+
|
|
33
|
+
TODO: List reference files in the references/ directory.
|
|
34
|
+
`;
|
|
35
|
+
export function registerGenerateSkillCommand(program) {
|
|
36
|
+
program
|
|
37
|
+
.command('skill <name>')
|
|
38
|
+
.description('Create a new skill skeleton with SKILL.md and references directory')
|
|
39
|
+
.action(async (name) => {
|
|
40
|
+
try {
|
|
41
|
+
const projectRoot = await resolveProjectRoot();
|
|
42
|
+
const skillDir = path.join(projectRoot, '.claude', 'skills', name);
|
|
43
|
+
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
44
|
+
const referencesDir = path.join(skillDir, 'references');
|
|
45
|
+
// Check if skill already exists
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(skillDir);
|
|
48
|
+
console.error(chalk.red(`Skill "${name}" already exists at ${skillDir}`));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Directory does not exist, proceed
|
|
53
|
+
}
|
|
54
|
+
await fs.mkdir(referencesDir, { recursive: true });
|
|
55
|
+
await fs.writeFile(skillMdPath, SKILL_MD_TEMPLATE(name), 'utf-8');
|
|
56
|
+
// Create a .gitkeep in references so the directory is tracked
|
|
57
|
+
await fs.writeFile(path.join(referencesDir, '.gitkeep'), '', 'utf-8');
|
|
58
|
+
console.log(chalk.green('Skill created successfully!'));
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(chalk.bold(' Skill:'), name);
|
|
61
|
+
console.log(chalk.bold(' Path:'), skillDir);
|
|
62
|
+
console.log(chalk.bold(' Files:'));
|
|
63
|
+
console.log(chalk.gray(' -'), 'SKILL.md');
|
|
64
|
+
console.log(chalk.gray(' -'), 'references/');
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(chalk.dim(` Edit ${path.join('.claude/skills', name, 'SKILL.md')} to define the skill.`));
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70
|
+
console.error(chalk.red('Failed to create skill:'), message);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=generate-skill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-skill.js","sourceRoot":"","sources":["../../../src/cli/commands/generate-skill.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAU9D"}
|