@loom-framework/core 0.1.0-alpha.91 → 0.1.0-alpha.92

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 +18 -12
  2. package/builtin-skills/loom/references/README.md +146 -89
  3. package/builtin-skills/loom/references/dashboard.md +101 -82
  4. package/builtin-skills/loom/references/data-model.md +38 -0
  5. package/dist/backend/index.js +1 -1
  6. package/dist/backend/index.js.map +1 -1
  7. package/dist/backend/routes/data.d.ts +8 -7
  8. package/dist/backend/routes/data.d.ts.map +1 -1
  9. package/dist/backend/routes/data.js +32 -6
  10. package/dist/backend/routes/data.js.map +1 -1
  11. package/dist/cli/commands/generate-dashboard.d.ts +2 -2
  12. package/dist/cli/commands/generate-dashboard.js +19 -19
  13. package/dist/cli/commands/generate-dashboard.js.map +1 -1
  14. package/dist/cli/commands/generate-page.d.ts.map +1 -1
  15. package/dist/cli/commands/generate-page.js +27 -29
  16. package/dist/cli/commands/generate-page.js.map +1 -1
  17. package/dist/cli/helpers/column-template.d.ts +1 -1
  18. package/dist/cli/helpers/column-template.d.ts.map +1 -1
  19. package/dist/cli/helpers/column-template.js +13 -6
  20. package/dist/cli/helpers/column-template.js.map +1 -1
  21. package/dist/cli/helpers/field-template.d.ts +1 -1
  22. package/dist/cli/helpers/field-template.d.ts.map +1 -1
  23. package/dist/cli/helpers/field-template.js +6 -1
  24. package/dist/cli/helpers/field-template.js.map +1 -1
  25. package/dist/config.d.ts +151 -9
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +3 -1
  28. package/dist/config.js.map +1 -1
  29. package/dist/dashboard-config.d.ts +1 -7
  30. package/dist/dashboard-config.d.ts.map +1 -1
  31. package/dist/dashboard-config.js +1 -30
  32. package/dist/dashboard-config.js.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/types/config.d.ts +2 -0
  38. package/dist/types/config.d.ts.map +1 -1
  39. package/package.json +1 -1
@@ -53,19 +53,13 @@ loom generate page <Name> --model <model-name>
53
53
 
54
54
  ### 4. 生成 Dashboard(数据概览)
55
55
 
56
- Dashboard 采用**两阶段**工作流:AI 生成配置 CLI 生成页面。
57
-
58
- **阶段 1:生成 dashboard.config.json**
59
-
60
- 根据 `loom.config.ts` 中的数据模型,站在用户角度设计 Dashboard,生成 `dashboard.config.json` 到项目根目录。设计原则和配置 Schema 详见 `references/dashboard.md`。
61
-
62
- **阶段 2:执行生成命令**
56
+ Dashboard 配置在 `loom.config.ts` `dashboards` 字段中定义。设计原则和配置 Schema 详见 `references/dashboard.md`。
63
57
 
64
58
  ```bash
65
59
  loom generate dashboard <name>
66
60
  ```
67
61
 
68
- 此命令自动生成 Dashboard 页面并接线 App.tsx。
62
+ 此命令从 `loom.config.ts` 的 `dashboards` 字段读取配置,自动生成 Dashboard 页面并接线 App.tsx。
69
63
 
70
64
  ### 5. 完善 AI Skill(必须)
71
65
 
@@ -94,7 +88,11 @@ loom dev
94
88
 
95
89
  ### 迭代:修改配置 → 重新生成
96
90
 
97
- 编辑 `loom.config.ts`(添加/修改模型或 AI 按钮)→ 重新执行步骤 3
91
+ 编辑 `loom.config.ts`(添加/修改模型或 AI 按钮)→ 重新执行步骤 3,加 `--force` 覆盖已有页面:
92
+
93
+ ```bash
94
+ loom generate page <Name> --model <model-name> --force
95
+ ```
98
96
 
99
97
  ### 内置 Skill 使用原则
100
98
 
@@ -109,7 +107,9 @@ loom dev
109
107
  | `loom dev` | 启动开发环境(`--skip-generate` `--backend-only` `--frontend-only`) |
110
108
  | `loom build` | 生产构建 |
111
109
  | `loom generate page <name> --model <model>` | 生成 CRUD 页面 + 应用 Skill(含 aiButtons 则自动集成) |
112
- | `loom generate dashboard <name>` | dashboard.config.json 生成 Dashboard 数据概览页面 |
110
+ | `loom generate page <name> --model <model> --force` | 覆盖已有页面 |
111
+ | `loom generate dashboard <name>` | 从 loom.config.ts dashboards 生成 Dashboard 页面(fallback dashboard.config.json) |
112
+ | `loom generate dashboard <name> --force` | 覆盖已有 Dashboard 页面 |
113
113
  | `loom generate capabilities` | 仅重新生成应用 Skill 的 `references/models.md` |
114
114
  | `loom generate skill <name>` | 生成自定义 Skill 脚架 |
115
115
 
@@ -149,6 +149,12 @@ export default defineConfig({
149
149
  prompt: '分析{{questionContent}},错误答案:{{wrongAnswer}}',
150
150
  placement: 'wrong_questions', // 可选:限制按钮只出现在指定模型页面,逗号分隔多个模型名;省略则出现在所有页面
151
151
  }],
152
+ dashboards: [{ // 可选:Dashboard 定义(不再使用独立的 dashboard.config.json)
153
+ name: 'overview', // 必填:唯一标识,同命名规则
154
+ description: '数据概览', // 可选
155
+ models: ['items'], // 必填:关联的数据模型
156
+ layout: [{ row: [{ type: 'stat', title: '总数', model: 'items', aggregate: 'count', span: 6 }] }],
157
+ }],
152
158
  });
153
159
  ```
154
160
 
@@ -156,9 +162,9 @@ export default defineConfig({
156
162
 
157
163
  ## API 路由格式
158
164
 
159
- 后端 REST API 路径为 `/api/v1/data/<model-name>`,支持:`GET`(列表)、`GET /:id`(单条)、`POST`(创建)、`PUT /:id`(更新)、`DELETE /:id`(删除)。
165
+ 后端 REST API 路径为 `/api/v1/data/<model-name>`,支持:`GET`(列表)、`GET /:id`(单条)、`POST`(创建)、`PUT /:id`(更新)、`DELETE /:id`(删除)、`GET /schema`(模型 Schema + AI 按钮)。
160
166
  AI 相关:`POST /api/v1/chat`(SSE 流式对话)、`GET /api/v1/sessions`(会话列表)。
161
- 前端通过 `useData('<model-name>')` 自动访问,无需手动拼接。
167
+ 前端通过 `useData('<model-name>')` 自动访问,无需手动拼接。`useSchema('<model-name>')` 获取模型字段和 AI 按钮定义,`filterOptions(schema, fieldName)` 和 `selectOptions(schema, fieldName)` 将 enum 值转为 antd 组件格式。
162
168
 
163
169
  **注意:** `sqlite` 适配器在 Node.js ≥ 25 时可能因 `better-sqlite3` 原生模块未编译而报错,改用 `filesystem` 即可。
164
170
 
@@ -1,128 +1,185 @@
1
- # Loom - AI-Native Full-Stack Framework
1
+ # Loom 开发 AI 平台
2
2
 
3
- 编织 AI 能力为应用织物
3
+ ## 30 秒理解 Loom
4
4
 
5
- ## 快速上手
5
+ 你写一个配置文件描述你的数据模型和 AI 按钮,Loom 生成一个可运行的平台。然后你和 AI 一起迭代它。
6
6
 
7
- ### 1. 安装 npm 包
8
-
9
- Loom 包含两个包:
7
+ ```
8
+ 你写的 Loom 生成的 你和 AI 继续做的
9
+ loom.config.ts → 页面 + API + AI 对话 → 改布局、加功能、调样式
10
+ ```
10
11
 
11
- - `@loom-framework/core` — CLI + 后端(全局安装以使用 `loom` 命令)
12
- - `@loom-framework/frontend-antd` — 前端组件库(项目运行时依赖,`loom init` 自动添加)
12
+ ## 开发方式
13
13
 
14
- ```bash
15
- npm install -g @loom-framework/core
16
- ```
14
+ Loom 项目的主要交互方式是**和 AI 对话**。你告诉 AI 想要什么,AI 通过 Loom Skill 知道怎么用 Loom 实现它。
17
15
 
18
- ### 2. 配置 Skill 到 Claude Code
16
+ 两个入口,同一个 AI:
19
17
 
20
- Loom Skill 随 npm 包发布,位于 `builtin-skills/loom/` 目录。复制到 Claude Code skill 目录:
18
+ - **终端 Claude Code** 适合搭建阶段、大范围代码修改
19
+ - **页面内对话框** — 适合日常操作数据、小范围页面调整
21
20
 
22
- ```bash
23
- # 从全局安装复制
24
- cp -r $(npm root -g)/@loom-framework/core/builtin-skills/loom ~/.claude/skills/loom
21
+ 两个入口背后是同一个 Claude Code 引擎、同一套项目上下文。在哪边改都行。
25
22
 
26
- # 或从项目内复制
27
- cp -r node_modules/@loom-framework/core/builtin-skills/loom ~/.claude/skills/loom
28
- ```
23
+ ## 第一个项目
29
24
 
30
- ### 3. 打开 Claude Code
25
+ 假设你要做一个**错题管理平台**。
31
26
 
32
- 启动 Claude Code,Skill 会被自动加载。
27
+ ### 1 步:告诉 AI 你的想法
33
28
 
34
- ### 4. 开始使用
29
+ Claude Code 中说:
35
30
 
36
- Claude Code 中输入以下内容触发 Loom Skill:
31
+ > loom 创建一个错题管理平台,项目名 wrong-questions,filesystem 适配器。
32
+ > 需要两个模型:错题记录(科目、题目内容、错误答案、正确答案、错误类型、是否掌握)和复习计划(错题ID、计划日期、状态)。
33
+ > AI 按钮要能分析错因和出类似题。
37
34
 
38
- ### 开始开发
35
+ AI 会读取 Loom Skill,自动执行:
39
36
 
40
- Claude Code 中输入以下内容即可触发 Loom Skill:
37
+ 1. `loom init wrong-questions --adapter filesystem` — 创建项目
38
+ 2. 编写 `loom.config.ts` — 把你的需求转成配置
39
+ 3. `loom generate page` — 生成页面
40
+ 4. `loom generate dashboard` — 生成数据概览
41
+ 5. 补充项目级 AI Skill — 让 AI 理解你的业务语义
41
42
 
42
- - "用 loom 开发一个 AI 平台"
43
- - "创建一个 loom 项目"
44
- - "为系统添加页面"
45
- - "配置 AI 按钮"
43
+ 你也可以自己跑命令,但通过 Loom Skill AI 来做更省事 — 它知道每一步该做什么。
46
44
 
47
- 示例对话:
45
+ ### 第 2 步:看看效果
48
46
 
49
- ```
50
- 用户: 用 loom 开发一个笔记管理系统,用 sqlite 存储,
51
- 需要 AI 总结和翻译功能
52
- Claude: [读取 SKILL.md] → 运行 loom init → 编辑 config →
53
- loom generate page → loom dev
47
+ ```bash
48
+ cd wrong-questions
49
+ loom dev
54
50
  ```
55
51
 
56
- ### 开发流程
52
+ 打开 http://localhost:5173 ,你的平台已经能用了:
53
+ - 左侧导航有「错题记录」和「复习计划」页面
54
+ - 每个页面有新增、编辑、删除、筛选
55
+ - 错题记录每行有 AI 按钮:分析错因、出类似题
56
+ - 右下角有 AI 对话框,可以自然语言操作数据
57
57
 
58
- 1. **创建项目**:`npx -p @loom-framework/core loom init <name> --description <desc> --adapter <adapter>`
59
- 2. **编辑配置**:在 `loom.config.ts` 中定义数据模型和 AI 按钮
60
- 3. **生成页面**:`loom generate page <Name> --model <model>`
61
- 4. **启动开发**:`loom dev` → http://localhost:5173
58
+ ### 3 步:继续和 AI
62
59
 
63
- ## 核心理念
60
+ 平台跑起来后,你主要的工作方式是对 AI 说需求:
64
61
 
65
- - **Prompt as Code**: 业务逻辑写在 Skill 的 Markdown 中,而非后端代码
66
- - **Schema-driven**: 一个数据实体定义 → Skill + CLI 命令 + REST API(自动同步)
67
- - **Zero Backend Logic**: 后端是透明代理,所有智能在 Skill
62
+ | 你说的 | AI 做的 |
63
+ |-------|--------|
64
+ | "帮我记一道数学错题,3+5=7,正确答案是8" | `loom data write` 创建记录 |
65
+ | "看看哪些错题还没掌握" | 查数据并展示 |
66
+ | "把这个页面改成卡片布局" | 修改页面 TSX |
67
+ | "加一个学期字段" | 改 loom.config.ts + 改页面 + 更新 Skill references |
68
+ | "加一个科目分布的饼图" | 修改 Dashboard 页面 |
68
69
 
69
- ## 三种交互类型
70
+ 这些操作在终端 Claude Code 和页面对话框里都能做。
70
71
 
71
- | 类型 | 说明 | 用户入口 | 自动化程度 |
72
- |------|------|---------|-----------|
73
- | AI Chat | 自然语言对话 | AppShell 右下角浮动按钮 | 开箱即用,无需配置 |
74
- | AI Buttons | 一键 AI 动作 | CRUD 页面操作列的 AI 下拉菜单 | 配置 aiButtons + generate page,自动集成 |
75
- | CRUD | 结构化数据管理 | 导航菜单页面 | 配置 model + generate page,自动生成 |
72
+ ## 配置变更
76
73
 
77
- ## 组件架构
74
+ 改了 `loom.config.ts`(加了字段、改了 enum),页面不会自动更新。告诉 AI 你改了什么,AI 会同步更新页面。
78
75
 
76
+ 如果你想重来一个干净的页面:
77
+
78
+ ```bash
79
+ loom generate page WrongQuestions --model wrong_questions --force
79
80
  ```
80
- AppShell
81
- ├── Layout (Sider + Content)
82
- └── AIBubble → ChatDrawer → ChatPanel
83
- ├── LoomChatProvider + useXChat
84
- ├── MessageContent (thinking / content / tool_call)
85
- └── Sender (输入框 + 文件上传)
86
-
87
- AIActionButton → AIContext.triggerAI() → ChatDrawer 自动提交
88
- useData 监听 loom:data-changed 事件自动刷新
81
+
82
+ `--force` 会用最新配置全量重建页面。页面不存在时直接 `loom generate page` 即可,不需要 `--force`。
83
+
84
+ ## 配置写法
85
+
86
+ `loom.config.ts` 是唯一配置源,包含数据模型、AI 按钮和 Dashboard 定义。完整的字段类型和高级选项见 `references/data-model.md`。
87
+
88
+ ```typescript
89
+ import { defineConfig } from '@loom-framework/core';
90
+
91
+ export default defineConfig({
92
+ project: { name: 'my-app', description: '我的应用' },
93
+ data: {
94
+ defaultAdapter: 'filesystem',
95
+ models: [{
96
+ name: 'tasks',
97
+ description: '任务管理',
98
+ fields: [
99
+ { name: 'title', type: 'string', required: true, description: '任务标题' },
100
+ { name: 'status', type: 'string', enum: ['待办', '进行中', '已完成'], default: '待办' },
101
+ { name: 'priority', type: 'string', enum: ['低', '中', '高'] },
102
+ ],
103
+ }],
104
+ },
105
+ aiButtons: [{
106
+ id: 'breakdown',
107
+ label: '拆解任务',
108
+ prompt: '请将这个任务拆解为可执行的子步骤:{{title}}',
109
+ placement: 'tasks',
110
+ }],
111
+ dashboards: [{
112
+ name: 'overview',
113
+ description: '数据概览',
114
+ models: ['tasks'],
115
+ layout: [
116
+ { row: [
117
+ { type: 'stat', title: '任务总数', model: 'tasks', aggregate: 'count', span: 6 },
118
+ { type: 'pie', title: '状态分布', model: 'tasks', groupBy: 'status', span: 8 },
119
+ { type: 'bar', title: '优先级分布', model: 'tasks', groupBy: 'priority', span: 8 },
120
+ ]}
121
+ ],
122
+ }],
123
+ });
89
124
  ```
90
125
 
91
- ### AI 按钮工作流
126
+ - AI 按钮的 `prompt` 支持 `{{fieldName}}` 插值,运行时替换为当前行的字段值
127
+ - `placement` 限制按钮只出现在指定模型页面,省略则全局显示
128
+ - Dashboard 的 17 种图表类型和配置 Schema 见 `references/dashboard.md`
92
129
 
93
- 1. 点击 AI 按钮 → `AIContext.triggerAI({ query, buttonId, context })`
94
- 2. ChatDrawer 打开并自动提交 prompt
95
- 3. AI 可能通过工具调用进行 CRUD 操作
96
- 4. done 事件触发 `loom:data-changed` → useData 自动 refresh
130
+ ## 项目级 AI Skill
97
131
 
98
- ### 前端组件导出
132
+ 每个项目的 `.claude/skills/<项目名>/SKILL.md` 是 AI 理解你业务的入口。`loom generate page` 会自动生成骨架,但需要你补充三个地方:
99
133
 
100
- | 组件/Hook | 说明 |
101
- |-----------|------|
102
- | `AppShell` | 应用布局框架,内含 AIBubble |
103
- | `ChatDrawer` | AI 对话抽屉(多会话、流式、thinking/tool_call) |
104
- | `AIBubble` | AI 浮动按钮(imperativeHandle: openWithQuery) |
105
- | `AIActionButton` | AI 动作按钮(通过 AIContext 触发 ChatDrawer) |
106
- | `AIContext` | AI 上下文(AppShell 提供 triggerAI) |
107
- | `useData` | 数据 CRUD hook(自动响应 loom:data-changed 事件) |
134
+ 1. **description**:写用户会说的话,如 `"记录错题", "查看错题", "分析错因"`
135
+ 2. **Overview**:2-3 句话描述平台做什么
136
+ 3. **Usage Scenarios**:5-10 个典型用户请求及对应的 `loom data` 命令
108
137
 
109
- ## 项目结构
138
+ 补充后,AI 在终端和对话框里都能理解"帮我查数学错题"这类业务请求。
110
139
 
140
+ ## 数据操作
141
+
142
+ 不打开页面也能操作数据。你可以自己跑命令,也可以让 AI 跑:
143
+
144
+ ```bash
145
+ loom data read wrong_questions --filter '{"subject":"数学"}' --limit 10
146
+ loom data write wrong_questions --data '{"subject":"数学","questionContent":"3+5=?","wrongAnswer":"7","correctAnswer":"8"}'
147
+ loom data update wrong_questions --id rec_xxx --data '{"isMastered":true}'
148
+ loom data delete wrong_questions --id rec_xxx
149
+ loom data schema wrong_questions # 查看字段结构
111
150
  ```
112
- my-loom-app/
113
- ├── loom.config.ts # 配置源
114
- ├── frontend/src/components/pages/ # 业务页面
115
- ├── backend/src/ # Fastify + AI 通信
116
- ├── .claude/skills/ # Skills
117
- ├── cli/src/commands/ # CLI 命令扩展
118
- ├── data/ # 数据存储
119
- └── .loom/ # 自动生成(gitignore)
120
- └── sessions/ # AI 会话持久化
151
+
152
+ ## 后端扩展
153
+
154
+ 需要自定义 API 路由时,编辑 `backend/src/index.ts`:
155
+
156
+ ```typescript
157
+ import { LoomServer } from '@loom-framework/core';
158
+
159
+ const server = new LoomServer({
160
+ projectRoot,
161
+ hooks: {
162
+ afterInit({ app, adapter }) {
163
+ app.get('/api/v1/stats/trend', async (req, reply) => { ... });
164
+ },
165
+ },
166
+ });
167
+ await server.initialize();
168
+ await server.start();
121
169
  ```
122
170
 
123
- ## 两个包
171
+ 不需要时就是原来的 3 行。
172
+
173
+ ## 常见问题
174
+
175
+ **loom 命令报错 "No loom.config.ts found"**
176
+ 在项目根目录下运行。
177
+
178
+ **better-sqlite3 加载失败**
179
+ `defaultAdapter` 改为 `'filesystem'`,或者 `cd node_modules/better-sqlite3 && npx node-gyp rebuild`。
180
+
181
+ **改了 loom.config.ts 页面没变**
182
+ 告诉 AI 你改了什么,AI 会同步更新页面。或者用 `--force` 重建。
124
183
 
125
- | | 说明 |
126
- |---|---|
127
- | `@loom-framework/core` | DataAdapter、LoomServer、CLI(loom / loom-server) |
128
- | `@loom-framework/frontend-antd` | 前端组件库:AppShell、ChatDrawer、AIActionButton、useData、AIContext |
184
+ **对话框里的 AI 能改代码吗**
185
+ 能。和终端 Claude Code 是同一个引擎,在哪边改都行。
@@ -1,10 +1,27 @@
1
1
  # 生成 Dashboard(数据概览页面)
2
2
 
3
- Dashboard 采用**两阶段**工作流:AI 设计配置 代码生成页面。
4
-
5
- ## 阶段 1:生成 dashboard.config.json
6
-
7
- 根据项目的 `loom.config.ts` 中的数据模型,站在用户角度设计 Dashboard,生成 `dashboard.config.json` 到项目根目录。
3
+ Dashboard `loom.config.ts` `dashboards` 字段中定义,然后通过 CLI 生成页面。
4
+
5
+ ## 配置 Dashboard
6
+
7
+ `loom.config.ts` 中添加 `dashboards` 字段:
8
+
9
+ ```typescript
10
+ import { defineConfig } from '@loom-framework/core';
11
+
12
+ export default defineConfig({
13
+ project: { name: 'my-app' },
14
+ data: { /* ... */ },
15
+ dashboards: [{
16
+ name: 'overview',
17
+ description: '数据概览',
18
+ models: ['items'],
19
+ layout: [
20
+ { row: [{ type: 'stat', title: '总数', model: 'items', aggregate: 'count', span: 6 }] },
21
+ ],
22
+ }],
23
+ });
24
+ ```
8
25
 
9
26
  **设计原则:**
10
27
  - 统计卡片放第一行(一眼看到关键数字)
@@ -15,29 +32,25 @@ Dashboard 采用**两阶段**工作流:AI 设计配置 → 代码生成页面
15
32
 
16
33
  ### 配置 Schema
17
34
 
18
- ```jsonc
35
+ ```typescript
19
36
  {
20
- "name": "overview", // 必填:Dashboard 标识(同模型命名规则)
21
- "description": "数据概览", // 可选:侧边栏展示名
22
- "models": ["model_a", "model_b"], // 必填:关联的数据模型名列表
23
- "layout": [ // 必填:布局定义,数组每项代表一行
24
- {
25
- "row": [ // 每行包含一组 widget
26
- {
27
- "type": "stat", // 图表类型,见下方图表类型表
28
- "title": "总数", // 业务语言标题
29
- "model": "model_a", // 数据模型名
30
- "span": 6, // 可选:Col 宽度(1-24),省略则自动均分
31
- "aggregate": "count", // 可选:count|sum|avg|min|max|ratio(默认 count)
32
- "field": "score", // aggregate 为 sum/avg/min/max 时必填:数值字段名
33
- "filter": { "status": "active" }, // 可选:聚合前筛选条件
34
- "groupBy": "category", // 可选:分组字段(string/enum/date/boolean
35
- "crossGroupBy": "grade", // 可选:第二分组字段,用于二维图表
36
- "interval": "month" // 可选:date 字段分组粒度 day|week|month(默认 month)
37
- }
38
- ]
39
- }
40
- ]
37
+ name: 'overview', // 必填:Dashboard 标识(同模型命名规则)
38
+ description: '数据概览', // 可选:侧边栏展示名
39
+ models: ['model_a'], // 必填:关联的数据模型名列表
40
+ layout: [{ // 必填:布局定义,数组每项代表一行
41
+ row: [{ // 每行包含一组 widget
42
+ type: 'stat', // 图表类型,见下方图表类型表
43
+ title: '总数', // 业务语言标题
44
+ model: 'model_a', // 数据模型名
45
+ span: 6, // 可选:Col 宽度(1-24),省略则自动均分
46
+ aggregate: 'count', // 可选:count|sum|avg|min|max|ratio(默认 count)
47
+ field: 'score', // aggregate 为 sum/avg/min/max 时必填:数值字段名
48
+ filter: { status: 'active' }, // 可选:聚合前筛选条件
49
+ groupBy: 'category', // 可选:分组字段(string/enum/date/boolean)
50
+ crossGroupBy: 'grade', // 可选:第二分组字段,用于二维图表
51
+ interval: 'month' // 可选:date 字段分组粒度 day|week|month(默认 month
52
+ }]
53
+ }]
41
54
  }
42
55
  ```
43
56
 
@@ -47,7 +60,7 @@ Dashboard 采用**两阶段**工作流:AI 设计配置 → 代码生成页面
47
60
  |------|------|------|
48
61
  | `aggregate` | `count\|sum\|avg\|min\|max\|ratio` | 聚合方式。`ratio` = 符合 filter 条件的记录数/总记录数,用于 gauge/liquid/stat 百分比 |
49
62
  | `field` | string | 聚合目标字段。仅 aggregate 为 sum/avg/min/max 时需要 |
50
- | `filter` | object | 筛选条件,键值对。如 `{"isMastered": true}` 筛选已掌握的记录 |
63
+ | `filter` | object | 筛选条件,键值对。如 `{isMastered: true}` 筛选已掌握的记录 |
51
64
  | `groupBy` | string | 主分组字段。string/enum 字段按值分组计数;boolean 字段自动映射为"是/否";date 字段按 interval 分段 |
52
65
  | `crossGroupBy` | string | 第二分组维度。配合 groupBy 使用,产生交叉分组数据,用于二维图表 |
53
66
  | `interval` | `day\|week\|month` | date 字段的分组粒度,仅 groupBy 指向 date 字段时有效 |
@@ -96,20 +109,20 @@ Dashboard 采用**两阶段**工作流:AI 设计配置 → 代码生成页面
96
109
  | `scatter` | 散点图 | `groupBy` | `{ groupBy: "subject" }` | G2 散点图(相关性/分布) |
97
110
  | `heatmap` | 热力图 | `groupBy` + `crossGroupBy` | `{ groupBy: "subject", crossGroupBy: "grade" }` | G2 热力图(密度/交叉分析,颜色深浅表示数量) |
98
111
 
99
- ## 阶段 2:执行 generate dashboard
112
+ ## 执行 generate dashboard
100
113
 
101
- 用户审查 `dashboard.config.json` 后,运行命令生成页面:
114
+ 配置好 `dashboards` 字段后,运行命令生成页面:
102
115
 
103
116
  ```bash
104
117
  loom generate dashboard <name>
105
118
  ```
106
119
 
107
120
  此命令会:
108
- 1. 读取并校验 `dashboard.config.json`
109
- 2. 验证引用的模型是否存在于 `loom.config.ts`
121
+ 1. `loom.config.ts` 的 `dashboards` 字段读取配置
122
+ 2. 验证引用的模型是否存在
110
123
  3. 生成 Dashboard TSX 页面(含 `useData` + `useChartData` + `DashboardChart`)
111
124
  4. 自动接线 App.tsx(添加 import、navItem、switch case)
112
- 5. 检测 `@antv/g2` 是否已安装,未安装则提示
125
+ 5. 检测 `@antv/g2` 是否已安装,未安装则自动安装
113
126
 
114
127
  **前置依赖:** Dashboard 使用 @antv/g2 渲染图表,需在 frontend 目录安装:
115
128
 
@@ -121,55 +134,61 @@ cd frontend && pnpm add @antv/g2
121
134
 
122
135
  以小学生错题管理平台为例,展示所有 15 种图表的配置:
123
136
 
124
- ```json
125
- {
126
- "name": "overview",
127
- "description": "错题数据概览",
128
- "models": ["wrong_questions", "review_plans"],
129
- "layout": [
130
- {
131
- "row": [
132
- { "type": "stat", "title": "错题总数", "model": "wrong_questions", "aggregate": "count", "span": 4 },
133
- { "type": "stat", "title": "掌握率", "model": "wrong_questions", "aggregate": "ratio", "filter": { "isMastered": true }, "span": 4 },
134
- { "type": "gauge", "title": "掌握率仪表盘", "model": "wrong_questions", "aggregate": "ratio", "filter": { "isMastered": true }, "span": 6 },
135
- { "type": "liquid", "title": "掌握率水波图", "model": "wrong_questions", "aggregate": "ratio", "filter": { "isMastered": true }, "span": 5 },
136
- { "type": "stat", "title": "平均复习次数", "model": "wrong_questions", "aggregate": "avg", "field": "reviewCount", "span": 5 }
137
- ]
138
- },
139
- {
140
- "row": [
141
- { "type": "pie", "title": "科目分布", "model": "wrong_questions", "groupBy": "subject", "span": 8 },
142
- { "type": "ring", "title": "难度分布", "model": "wrong_questions", "groupBy": "difficulty", "span": 8 },
143
- { "type": "bar", "title": "错误类型分布", "model": "wrong_questions", "groupBy": "errorType", "span": 8 }
144
- ]
145
- },
146
- {
147
- "row": [
148
- { "type": "line", "title": "错题录入趋势", "model": "wrong_questions", "groupBy": "createdAt", "interval": "month", "span": 12 },
149
- { "type": "area", "title": "复习计划趋势", "model": "review_plans", "groupBy": "createdAt", "interval": "month", "span": 12 }
150
- ]
151
- },
152
- {
153
- "row": [
154
- { "type": "radar", "title": "各科目概览", "model": "wrong_questions", "groupBy": "subject", "span": 8 },
155
- { "type": "stacked_bar", "title": "科目×错误类型", "model": "wrong_questions", "groupBy": "subject", "crossGroupBy": "errorType", "span": 8 },
156
- { "type": "grouped_bar", "title": "科目×难度", "model": "wrong_questions", "groupBy": "subject", "crossGroupBy": "difficulty", "span": 8 }
157
- ]
158
- },
159
- {
160
- "row": [
161
- { "type": "scatter", "title": "科目分布散点", "model": "wrong_questions", "groupBy": "subject", "span": 8 },
162
- { "type": "funnel", "title": "错误类型漏斗", "model": "wrong_questions", "groupBy": "errorType", "span": 8 },
163
- { "type": "treemap", "title": "科目×难度树图", "model": "wrong_questions", "groupBy": "subject", "crossGroupBy": "difficulty", "span": 8 }
164
- ]
165
- },
166
- {
167
- "row": [
168
- { "type": "heatmap", "title": "科目×年级热力图", "model": "wrong_questions", "groupBy": "subject", "crossGroupBy": "grade", "span": 24 }
169
- ]
170
- }
171
- ]
172
- }
137
+ ```typescript
138
+ import { defineConfig } from '@loom-framework/core';
139
+
140
+ export default defineConfig({
141
+ project: { name: 'wrong-questions' },
142
+ data: { /* ... */ },
143
+ dashboards: [{
144
+ name: 'overview',
145
+ description: '错题数据概览',
146
+ models: ['wrong_questions', 'review_plans'],
147
+ layout: [
148
+ {
149
+ row: [
150
+ { type: 'stat', title: '错题总数', model: 'wrong_questions', aggregate: 'count', span: 4 },
151
+ { type: 'stat', title: '掌握率', model: 'wrong_questions', aggregate: 'ratio', filter: { isMastered: true }, span: 4 },
152
+ { type: 'gauge', title: '掌握率仪表盘', model: 'wrong_questions', aggregate: 'ratio', filter: { isMastered: true }, span: 6 },
153
+ { type: 'liquid', title: '掌握率水波图', model: 'wrong_questions', aggregate: 'ratio', filter: { isMastered: true }, span: 5 },
154
+ { type: 'stat', title: '平均复习次数', model: 'wrong_questions', aggregate: 'avg', field: 'reviewCount', span: 5 }
155
+ ]
156
+ },
157
+ {
158
+ row: [
159
+ { type: 'pie', title: '科目分布', model: 'wrong_questions', groupBy: 'subject', span: 8 },
160
+ { type: 'ring', title: '难度分布', model: 'wrong_questions', groupBy: 'difficulty', span: 8 },
161
+ { type: 'bar', title: '错误类型分布', model: 'wrong_questions', groupBy: 'errorType', span: 8 }
162
+ ]
163
+ },
164
+ {
165
+ row: [
166
+ { type: 'line', title: '错题录入趋势', model: 'wrong_questions', groupBy: 'createdAt', interval: 'month', span: 12 },
167
+ { type: 'area', title: '复习计划趋势', model: 'review_plans', groupBy: 'createdAt', interval: 'month', span: 12 }
168
+ ]
169
+ },
170
+ {
171
+ row: [
172
+ { type: 'radar', title: '各科目概览', model: 'wrong_questions', groupBy: 'subject', span: 8 },
173
+ { type: 'stacked_bar', title: '科目×错误类型', model: 'wrong_questions', groupBy: 'subject', crossGroupBy: 'errorType', span: 8 },
174
+ { type: 'grouped_bar', title: '科目×难度', model: 'wrong_questions', groupBy: 'subject', crossGroupBy: 'difficulty', span: 8 }
175
+ ]
176
+ },
177
+ {
178
+ row: [
179
+ { type: 'scatter', title: '科目分布散点', model: 'wrong_questions', groupBy: 'subject', span: 8 },
180
+ { type: 'funnel', title: '错误类型漏斗', model: 'wrong_questions', groupBy: 'errorType', span: 8 },
181
+ { type: 'treemap', title: '科目×难度树图', model: 'wrong_questions', groupBy: 'subject', crossGroupBy: 'difficulty', span: 8 }
182
+ ]
183
+ },
184
+ {
185
+ row: [
186
+ { type: 'heatmap', title: '科目×年级热力图', model: 'wrong_questions', groupBy: 'subject', crossGroupBy: 'grade', span: 24 }
187
+ ]
188
+ }
189
+ ]
190
+ }],
191
+ });
173
192
  ```
174
193
 
175
194
  ## 追加自定义图表
@@ -75,4 +75,42 @@ data: {
75
75
  models: [...],
76
76
  sqlite: { filename: 'loom.db' }, // optional, default 'loom.db'
77
77
  }
78
+ ```
79
+
80
+ ## DashboardConfig
81
+
82
+ ```typescript
83
+ dashboards: [{
84
+ name: 'overview', // ^[a-zA-Z][a-zA-Z0-9_]*$, same naming rules as model
85
+ description: '数据概览', // optional: shown in sidebar navigation
86
+ models: ['items'], // required: data models included in this dashboard
87
+ layout: [{ // required: at least one row
88
+ row: [{ // at least one widget per row
89
+ type: 'stat' | 'pie' | 'ring' | 'bar' | 'line' | 'area' | 'scatter' | 'radar' | 'funnel' | 'treemap' | 'gauge' | 'liquid' | 'heatmap' | 'stacked_bar' | 'grouped_bar',
90
+ title: '任务总数', // required: display title
91
+ model: 'items', // required: data model name
92
+ span: 6, // optional: Ant Design Grid Col span (1-24), auto-equal-split if omitted
93
+ groupBy: 'status', // optional: group by field for aggregation
94
+ crossGroupBy: 'priority', // optional: second group-by for 2D charts (heatmap, stacked_bar, grouped_bar)
95
+ aggregate: 'count', // optional: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'ratio', default count
96
+ field: 'score', // optional: numeric field for sum/avg/min/max
97
+ filter: { status: 'active' }, // optional: pre-aggregation filter
98
+ interval: 'month', // optional: 'day' | 'week' | 'month' for date groupBy
99
+ }],
100
+ }],
101
+ }]
102
+ ```
103
+
104
+ ## useSchema Runtime API
105
+
106
+ Generated pages use `useSchema(model)` to fetch model schema and AI buttons at runtime:
107
+
108
+ ```typescript
109
+ const { schema, loading, error } = useSchema('items');
110
+ // schema.fields — field definitions with enum values
111
+ // schema.aiButtons — AI buttons filtered by placement
112
+
113
+ // Convert schema enum to antd formats:
114
+ filterOptions(schema, 'status') // → [{ text: 'active', value: 'active' }, ...]
115
+ selectOptions(schema, 'status') // → [{ label: 'active', value: 'active' }, ...]
78
116
  ```
@@ -86,7 +86,7 @@ export class LoomServer {
86
86
  }
87
87
  // Register routes
88
88
  registerHealthRoute(this.fastify, this.adapter);
89
- registerDataRoutes(this.fastify, this.adapter);
89
+ registerDataRoutes(this.fastify, this.adapter, this.config);
90
90
  registerUploadRoutes(this.fastify, { projectRoot: this.projectRoot });
91
91
  registerChatRoutes(this.fastify, {
92
92
  engine: this.engine,