@loom-framework/core 0.1.0-alpha.147 → 0.1.0-alpha.149

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.
Files changed (39) hide show
  1. package/builtin-skills/loom/SKILL.md +3 -1
  2. package/builtin-skills/loom/references/README.md +1 -1
  3. package/dist/cli/commands/generate-page.d.ts.map +1 -1
  4. package/dist/cli/commands/generate-page.js +1 -6
  5. package/dist/cli/commands/generate-page.js.map +1 -1
  6. package/dist/cli/commands/generate-system-settings.d.ts +9 -0
  7. package/dist/cli/commands/generate-system-settings.d.ts.map +1 -0
  8. package/dist/cli/commands/generate-system-settings.js +88 -0
  9. package/dist/cli/commands/generate-system-settings.js.map +1 -0
  10. package/dist/cli/commands/generate.d.ts.map +1 -1
  11. package/dist/cli/commands/generate.js +2 -0
  12. package/dist/cli/commands/generate.js.map +1 -1
  13. package/dist/cli/commands/init.d.ts.map +1 -1
  14. package/dist/cli/commands/init.js +21 -0
  15. package/dist/cli/commands/init.js.map +1 -1
  16. package/dist/cli/helpers/app-tsx-wiring.d.ts +8 -4
  17. package/dist/cli/helpers/app-tsx-wiring.d.ts.map +1 -1
  18. package/dist/cli/helpers/app-tsx-wiring.js +232 -136
  19. package/dist/cli/helpers/app-tsx-wiring.js.map +1 -1
  20. package/dist/cli/templates/frontend-entry.d.ts.map +1 -1
  21. package/dist/cli/templates/frontend-entry.js +0 -3
  22. package/dist/cli/templates/frontend-entry.js.map +1 -1
  23. package/dist/cli/templates/index.d.ts +2 -0
  24. package/dist/cli/templates/index.d.ts.map +1 -1
  25. package/dist/cli/templates/index.js +2 -0
  26. package/dist/cli/templates/index.js.map +1 -1
  27. package/dist/cli/templates/model-management-page.d.ts +9 -0
  28. package/dist/cli/templates/model-management-page.d.ts.map +1 -0
  29. package/dist/cli/templates/model-management-page.js +298 -0
  30. package/dist/cli/templates/model-management-page.js.map +1 -0
  31. package/dist/cli/templates/skill-management-page.d.ts +9 -0
  32. package/dist/cli/templates/skill-management-page.d.ts.map +1 -0
  33. package/dist/cli/templates/skill-management-page.js +476 -0
  34. package/dist/cli/templates/skill-management-page.js.map +1 -0
  35. package/dist/cli/templates/system-settings-page.d.ts +10 -0
  36. package/dist/cli/templates/system-settings-page.d.ts.map +1 -0
  37. package/dist/cli/templates/system-settings-page.js +768 -0
  38. package/dist/cli/templates/system-settings-page.js.map +1 -0
  39. package/package.json +1 -1
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Model Management page template
3
+ *
4
+ * Generates ModelManagement page as a local source file.
5
+ * Page imports sub-components from @loom-framework/frontend-antd and
6
+ * registers its own i18n keys via registerMessages().
7
+ */
8
+ export function modelManagementPageTemplate() {
9
+ return `import React, { useState, useEffect, useCallback } from 'react';
10
+ import { Card, Table, Button, Modal, Form, Input, Switch, Tag, Space, Typography, message, theme, Breadcrumb, Flex } from 'antd';
11
+ import { PlusOutlined, EditOutlined, DeleteOutlined, RobotOutlined, HomeOutlined, AppstoreAddOutlined } from '@ant-design/icons';
12
+ import { useLocale, useAppShell, registerMessages } from '@loom-framework/frontend-antd';
13
+
14
+ registerMessages('zh-CN', {
15
+ 'model.title': '模型管理',
16
+ 'model.addModel': '添加模型',
17
+ 'model.addEngine': '添加AI引擎',
18
+ 'model.editModel': '编辑模型',
19
+ 'model.modelName': '模型名',
20
+ 'model.authToken': '认证令牌',
21
+ 'model.baseUrl': 'API 地址',
22
+ 'model.setDefault': '设为默认',
23
+ 'model.default': '默认',
24
+ 'model.engine': 'AI 引擎',
25
+ 'model.configured': '已配置',
26
+ 'model.notSet': '未设置',
27
+ 'model.deleteConfirm': '确认删除模型 "{name}"?',
28
+ 'model.deleteSuccess': '模型已删除',
29
+ 'model.addSuccess': '模型已添加',
30
+ 'model.updateSuccess': '模型已更新',
31
+ 'model.saveFailed': '保存模型失败',
32
+ 'model.loadFailed': '加载模型失败',
33
+ 'model.nameRequired': '请输入模型名称',
34
+ 'model.baseUrlRequired': '请输入 API 地址',
35
+ 'model.tokenMasked': '留空保持当前值不变',
36
+ 'model.enterToken': '输入认证令牌',
37
+ });
38
+
39
+ registerMessages('en-US', {
40
+ 'model.title': 'Model Management',
41
+ 'model.addModel': 'Add Model',
42
+ 'model.addEngine': 'Add AI Engine',
43
+ 'model.editModel': 'Edit Model',
44
+ 'model.modelName': 'Model Name',
45
+ 'model.authToken': 'Auth Token',
46
+ 'model.baseUrl': 'API Base URL',
47
+ 'model.setDefault': 'Set as Default',
48
+ 'model.default': 'Default',
49
+ 'model.engine': 'AI Engine',
50
+ 'model.configured': 'Configured',
51
+ 'model.notSet': 'Not Set',
52
+ 'model.deleteConfirm': 'Delete model "{name}"?',
53
+ 'model.deleteSuccess': 'Model deleted',
54
+ 'model.addSuccess': 'Model added',
55
+ 'model.updateSuccess': 'Model updated',
56
+ 'model.saveFailed': 'Failed to save model',
57
+ 'model.loadFailed': 'Failed to load models',
58
+ 'model.nameRequired': 'Model name is required',
59
+ 'model.baseUrlRequired': 'Base URL is required',
60
+ 'model.tokenMasked': 'Leave empty to keep current',
61
+ 'model.enterToken': 'Enter auth token',
62
+ });
63
+
64
+ interface ModelItem {
65
+ name: string;
66
+ authToken?: string;
67
+ hasAuthToken?: boolean;
68
+ baseUrl?: string;
69
+ isDefault: boolean;
70
+ }
71
+
72
+ const API = '/api/v1/ai/models';
73
+
74
+ export default function ModelManagementPage(): React.ReactElement {
75
+ const { token } = theme.useToken();
76
+ const { t } = useLocale();
77
+ const { breadcrumbs, onNavClick } = useAppShell();
78
+ const [models, setModels] = useState<ModelItem[]>([]);
79
+ const [loading, setLoading] = useState(false);
80
+ const [modalOpen, setModalOpen] = useState(false);
81
+ const [editingModel, setEditingModel] = useState<ModelItem | null>(null);
82
+ const [form] = Form.useForm();
83
+
84
+ const fetchModels = useCallback(async () => {
85
+ setLoading(true);
86
+ try {
87
+ const res = await fetch(API);
88
+ const data = await res.json();
89
+ setModels(Array.isArray(data) ? data : []);
90
+ } catch {
91
+ message.error(t('model.loadFailed') || 'Failed to load models');
92
+ } finally {
93
+ setLoading(false);
94
+ }
95
+ }, []);
96
+
97
+ useEffect(() => { fetchModels(); }, [fetchModels]);
98
+
99
+ const handleSave = async () => {
100
+ try {
101
+ const values = await form.validateFields();
102
+ let updatedModels = models.map(m => ({ ...m, isDefault: false }));
103
+
104
+ if (editingModel) {
105
+ updatedModels = updatedModels.map(m =>
106
+ m.name === editingModel.name ? { ...m, ...values, hasAuthToken: values.authToken ? true : m.hasAuthToken } : m
107
+ );
108
+ } else {
109
+ updatedModels.push({ ...values, hasAuthToken: !!values.authToken });
110
+ }
111
+
112
+ if (values.isDefault) {
113
+ updatedModels = updatedModels.map(m => ({ ...m, isDefault: m.name === values.name }));
114
+ } else if (!updatedModels.some(m => m.isDefault)) {
115
+ updatedModels[0].isDefault = true;
116
+ }
117
+
118
+ const res = await fetch(API, {
119
+ method: 'PUT',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: JSON.stringify({ models: updatedModels }),
122
+ });
123
+
124
+ if (res.ok) {
125
+ const data = await res.json();
126
+ setModels(Array.isArray(data) ? data : []);
127
+ message.success(editingModel ? (t('model.updateSuccess') || 'Model updated') : (t('model.addSuccess') || 'Model added'));
128
+ } else {
129
+ message.error(t('model.saveFailed') || 'Failed to save model');
130
+ }
131
+
132
+ setModalOpen(false);
133
+ setEditingModel(null);
134
+ form.resetFields();
135
+ } catch { /* validation error */ }
136
+ };
137
+
138
+ const handleDelete = async (name: string) => {
139
+ Modal.confirm({
140
+ title: t('model.deleteConfirm')?.replace('{name}', name) || \`Delete model "\${name}"?\`,
141
+ onOk: async () => {
142
+ const updatedModels = models.filter(m => m.name !== name);
143
+ if (updatedModels.length > 0 && !updatedModels.some(m => m.isDefault)) {
144
+ updatedModels[0].isDefault = true;
145
+ }
146
+
147
+ const res = await fetch(API, {
148
+ method: 'PUT',
149
+ headers: { 'Content-Type': 'application/json' },
150
+ body: JSON.stringify({ models: updatedModels }),
151
+ });
152
+
153
+ if (res.ok) {
154
+ const data = await res.json();
155
+ setModels(Array.isArray(data) ? data : []);
156
+ message.success(t('model.deleteSuccess') || 'Model deleted');
157
+ }
158
+ },
159
+ });
160
+ };
161
+
162
+ const openEditModal = (model: ModelItem) => {
163
+ setEditingModel(model);
164
+ form.setFieldsValue({
165
+ name: model.name,
166
+ authToken: model.authToken,
167
+ baseUrl: model.baseUrl,
168
+ isDefault: model.isDefault,
169
+ });
170
+ setModalOpen(true);
171
+ };
172
+
173
+ const openAddModal = () => {
174
+ setEditingModel(null);
175
+ form.resetFields();
176
+ form.setFieldsValue({ isDefault: models.length === 0 });
177
+ setModalOpen(true);
178
+ };
179
+
180
+ const columns = [
181
+ {
182
+ title: t('model.modelName') || 'Model Name',
183
+ dataIndex: 'name',
184
+ key: 'name',
185
+ render: (name: string, record: ModelItem) => (
186
+ <Space>
187
+ <span style={{ fontWeight: token.fontWeightStrong }}>{name}</span>
188
+ {record.isDefault && <Tag color="blue">{t('model.default') || 'Default'}</Tag>}
189
+ </Space>
190
+ ),
191
+ },
192
+ {
193
+ title: t('model.baseUrl') || 'API Base URL',
194
+ dataIndex: 'baseUrl',
195
+ key: 'baseUrl',
196
+ render: (url: string) => url ? <Typography.Text copyable={{ text: url }} style={{ fontSize: token.fontSizeSM }}>{url}</Typography.Text> : <Typography.Text type="secondary">—</Typography.Text>,
197
+ },
198
+ {
199
+ title: t('model.authToken') || 'Auth Token',
200
+ dataIndex: 'hasAuthToken',
201
+ key: 'hasAuthToken',
202
+ width: 120,
203
+ render: (has: boolean) => has ? <Tag color="green">{t('model.configured') || 'Configured'}</Tag> : <Tag>{t('model.notSet') || 'Not Set'}</Tag>,
204
+ },
205
+ {
206
+ title: t('common.action') || 'Action',
207
+ key: 'action',
208
+ width: 120,
209
+ render: (_: unknown, record: ModelItem) => (
210
+ <Space size={0}>
211
+ <Button type="text" size="small" icon={<EditOutlined />} onClick={() => openEditModal(record)} />
212
+ {!record.isDefault && (
213
+ <Button type="text" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.name)} />
214
+ )}
215
+ </Space>
216
+ ),
217
+ },
218
+ ];
219
+
220
+ return (
221
+ <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
222
+ <Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
223
+ <Breadcrumb items={[{ title: <HomeOutlined onClick={() => onNavClick?.('')} style={{ cursor: 'pointer' }} /> }, ...(breadcrumbs || []).map(b => ({ title: b.path ? <a onClick={() => onNavClick?.(b.path!)}>{b.title}</a> : b.title }))]} />
224
+ <Button type="primary" icon={<AppstoreAddOutlined />} disabled>
225
+ {t('model.addEngine') || 'Add AI Engine'}
226
+ </Button>
227
+ </Flex>
228
+ <Card
229
+ title={
230
+ <Space>
231
+ <RobotOutlined />
232
+ <span>Claude Code</span>
233
+ <Typography.Text type="secondary" style={{ fontWeight: 'normal' }}>
234
+ {t('model.engine') || 'AI Engine'}
235
+ </Typography.Text>
236
+ </Space>
237
+ }
238
+ extra={
239
+ <Button type="primary" size="small" icon={<PlusOutlined />} onClick={openAddModal}>
240
+ {t('model.addModel') || 'Add Model'}
241
+ </Button>
242
+ }
243
+ >
244
+ <Table
245
+ dataSource={models}
246
+ columns={columns}
247
+ rowKey="name"
248
+ loading={loading}
249
+ pagination={false}
250
+ size="small"
251
+ />
252
+ </Card>
253
+
254
+ <Modal
255
+ title={editingModel ? (t('model.editModel') || 'Edit Model') : (t('model.addModel') || 'Add Model')}
256
+ open={modalOpen}
257
+ onOk={handleSave}
258
+ onCancel={() => { setModalOpen(false); setEditingModel(null); form.resetFields(); }}
259
+ okText={t('common.save') || 'Save'}
260
+ cancelText={t('common.cancel') || 'Cancel'}
261
+ destroyOnClose
262
+ >
263
+ <Form form={form} layout="vertical" style={{ marginTop: token.marginMD }}>
264
+ <Form.Item
265
+ name="name"
266
+ label={t('model.modelName') || 'Model Name'}
267
+ rules={[{ required: true, message: t('model.nameRequired') || 'Model name is required' }]}
268
+ >
269
+ <Input disabled={!!editingModel} placeholder="e.g. GLM-5.1, Kimi-K2.5" />
270
+ </Form.Item>
271
+ <Form.Item
272
+ name="baseUrl"
273
+ label={t('model.baseUrl') || 'API Base URL'}
274
+ rules={[{ required: true, message: t('model.baseUrlRequired') || 'Base URL is required' }]}
275
+ >
276
+ <Input placeholder="https://api.example.com" />
277
+ </Form.Item>
278
+ <Form.Item
279
+ name="authToken"
280
+ label={t('model.authToken') || 'Auth Token'}
281
+ >
282
+ <Input.Password placeholder={editingModel?.hasAuthToken ? t('model.tokenMasked') || 'Leave empty to keep current' : t('model.enterToken') || 'Enter auth token'} />
283
+ </Form.Item>
284
+ <Form.Item
285
+ name="isDefault"
286
+ label={t('model.setDefault') || 'Set as Default'}
287
+ valuePropName="checked"
288
+ >
289
+ <Switch />
290
+ </Form.Item>
291
+ </Form>
292
+ </Modal>
293
+ </div>
294
+ );
295
+ }
296
+ `;
297
+ }
298
+ //# sourceMappingURL=model-management-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-management-page.js","sourceRoot":"","sources":["../../../src/cli/templates/model-management-page.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,2BAA2B;IACzC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+RR,CAAC;AACF,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Skill Management page template
3
+ *
4
+ * Generates SkillManagement page as a local source file.
5
+ * Page imports sub-components from @loom-framework/frontend-antd and
6
+ * registers its own i18n keys via registerMessages().
7
+ */
8
+ export declare function skillManagementPageTemplate(): string;
9
+ //# sourceMappingURL=skill-management-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-management-page.d.ts","sourceRoot":"","sources":["../../../src/cli/templates/skill-management-page.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,wBAAgB,2BAA2B,IAAI,MAAM,CAmdpD"}