draftgo-cli 3.0.29 → 3.0.35
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/LICENSE +21 -0
- package/README.md +41 -139
- package/package.json +10 -2
- package/resources/skill/SKILL.md +61 -184
- package/resources/skill/init/SKILL.md +18 -66
- package/resources/skill/manifest.json +34 -0
- package/resources/skill/pull/SKILL.md +18 -52
- package/resources/skill/push/SKILL.md +30 -282
- package/resources/skill/references/aihub.md +86 -0
- package/resources/skill/{quickref → references}/api-endpoints.md +39 -13
- package/resources/skill/references/api.json +20248 -0
- package/resources/skill/{quickref → references}/app-api.md +40 -0
- package/resources/skill/{core → references}/architecture.md +2 -2
- package/resources/skill/references/chat-sdk.md +201 -0
- package/resources/skill/references/custom-services.md +308 -0
- package/resources/skill/{specs → references}/data.md +5 -5
- package/resources/skill/{rules → references}/frontend.md +138 -32
- package/resources/skill/{core → references}/modules.md +7 -5
- package/resources/skill/references/parallel.md +48 -0
- package/resources/skill/{specs → references}/runtime.md +1 -1
- package/resources/skill/scripts/draftgo_push.py +80 -12
- package/resources/skill/story/SKILL.md +11 -16
- package/src/cli.js +13 -7
- package/src/commandRegistry.js +34 -0
- package/src/commands/api.js +153 -8
- package/src/commands/help.js +24 -29
- package/src/commands/init.js +17 -18
- package/src/commands/local.js +9 -3
- package/src/commands/sync.js +1 -1
- package/src/commands/update.js +40 -12
- package/src/index.js +13 -57
- package/src/localdev/compose.js +44 -200
- package/src/localdev/index.js +116 -216
- package/src/localdev/mysqlClient.js +12 -9
- package/src/localdev/services.js +163 -0
- package/src/projectConfig.js +1 -1
- package/src/projectMap.js +17 -80
- package/src/skill.js +1 -1
- package/src/updateCheck.js +2 -12
- package/resources/skill/practices/anti-patterns.md +0 -80
- package/resources/skill/practices/best-practices.md +0 -60
- package/resources/skill/practices/dev-declaration.md +0 -114
- package/resources/skill/quickref/api.json +0 -17784
- package/resources/skill/rules/dev-workflow.md +0 -749
- package/resources/skill/rules/parallel.md +0 -263
- package/resources/skill/scripts/__pycache__/draftgo_pull.cpython-312.pyc +0 -0
- package/resources/skill/scripts/__pycache__/draftgo_push.cpython-312.pyc +0 -0
- package/resources/skill/specs/custom-services.md +0 -199
- package/src/commands/doctor.js +0 -54
- package/src/commands/new.js +0 -186
- package/src/commands/projectScript.js +0 -37
- package/src/commands/upgrade.js +0 -52
- /package/resources/skill/{specs → references}/db-relations.md +0 -0
- /package/resources/skill/{rules → references}/debugging-syntax.md +0 -0
- /package/resources/skill/{specs → references}/security.md +0 -0
- /package/resources/skill/{specs → references}/ui-protocol.md +0 -0
|
@@ -8,13 +8,11 @@ version: 2.0.0
|
|
|
8
8
|
|
|
9
9
|
> 根 `SKILL.md` 是完整核心规则源;本文件只展开前端实现方法。若表述冲突,以根 `SKILL.md` 为准,不额外叠加固定验收动作。
|
|
10
10
|
|
|
11
|
-
> 本文件只规定前端**操作性规则**,已剥离的内容见:
|
|
12
|
-
> 架构原理 → `{{SKILL_DIR}}/
|
|
13
|
-
> App API / Token / 路由 / 运行时 → `{{SKILL_DIR}}/
|
|
14
|
-
> 动态 DB / filters → `{{SKILL_DIR}}/
|
|
15
|
-
> 开发禁区 → `{{SKILL_DIR}}/
|
|
16
|
-
|
|
17
|
-
新建/重做前端界面、明显 UI 设计任务或需要整体视觉判断时,若本地 Agent 存在前端 UI Skills,优先调用;小修、文案、链接、局部样式调整不必调用。
|
|
11
|
+
> 本文件只规定前端**操作性规则**,已剥离的内容见:
|
|
12
|
+
> 架构原理 → `{{SKILL_DIR}}/references/architecture.md`
|
|
13
|
+
> App API / Token / 路由 / 运行时 → `{{SKILL_DIR}}/references/runtime.md` · `{{SKILL_DIR}}/references/app-api.md`
|
|
14
|
+
> 动态 DB / filters → `{{SKILL_DIR}}/references/data.md`
|
|
15
|
+
> 开发禁区 → `{{SKILL_DIR}}/references/security.md`
|
|
18
16
|
|
|
19
17
|
---
|
|
20
18
|
|
|
@@ -32,7 +30,6 @@ version: 2.0.0
|
|
|
32
30
|
### 操作型页面布局(强制)
|
|
33
31
|
|
|
34
32
|
后台管理、表格、列表等操作型页面用**工作台布局**,不用文档流堆叠:
|
|
35
|
-
强烈建议直接使用antd的组件
|
|
36
33
|
```css
|
|
37
34
|
.workbench-page { min-height:100vh; display:flex; flex-direction:column; overflow:hidden; }
|
|
38
35
|
.workbench-toolbar { flex-shrink:0; }
|
|
@@ -44,11 +41,51 @@ version: 2.0.0
|
|
|
44
41
|
- 分页/操作栏/保存栏固定在工作区底部,不随数据量上浮
|
|
45
42
|
- 滚动发生在数据区,不是整页
|
|
46
43
|
|
|
44
|
+
### 表格工作区布局
|
|
45
|
+
|
|
46
|
+
所有数据表格页面,无论使用原生表格、AntD 或其他组件库,都遵守同一布局契约:
|
|
47
|
+
|
|
48
|
+
- 页面根容器占满可用视口或 iframe 内容区,使用 `height: 100%`、`height: 100vh` 或已知导航高度的 `calc(...)`;不能由表格内容决定页面高度。
|
|
49
|
+
- 表格内容区占据工具栏和底部控制区以外的所有剩余空间:`flex: 1; min-height: 0`。
|
|
50
|
+
- 纵向和横向滚动只能发生在表格内容区。横向滚动条必须紧贴在分页或批量操作栏上方,不能落在长列表的末尾。
|
|
51
|
+
- 分页、批量操作栏、保存栏等底部控制区使用 `flex-shrink: 0`,始终停在工作区底部;数据只有一行或没有数据时也不向上漂移。
|
|
52
|
+
- 禁止使用 `min-height` 加自然文档流模拟该布局,也不要让 `body`、页面根和表格内容区同时滚动。
|
|
53
|
+
|
|
54
|
+
通用骨架:
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<main class="table-workspace">
|
|
58
|
+
<header class="table-toolbar">...</header>
|
|
59
|
+
<section class="table-viewport">
|
|
60
|
+
<table>...</table>
|
|
61
|
+
</section>
|
|
62
|
+
<footer class="table-footer">...</footer>
|
|
63
|
+
</main>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```css
|
|
67
|
+
.table-workspace {
|
|
68
|
+
height: 100%;
|
|
69
|
+
min-height: 0;
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
}
|
|
74
|
+
.table-toolbar,
|
|
75
|
+
.table-footer { flex-shrink: 0; }
|
|
76
|
+
.table-viewport {
|
|
77
|
+
flex: 1;
|
|
78
|
+
min-height: 0;
|
|
79
|
+
overflow: auto; /* X scrollbar remains immediately above .table-footer */
|
|
80
|
+
}
|
|
81
|
+
.table-viewport > table { min-width: max-content; }
|
|
82
|
+
```
|
|
83
|
+
|
|
47
84
|
### 表格数据区与分页钉底
|
|
48
85
|
|
|
49
86
|
当页面主体是表格、日志列表、后台数据管理页时,必须先做固定高度的 flex column 容器,再放表格。不要让分页跟随表格内容高度往下跑,也不要让整页滚动。
|
|
50
87
|
|
|
51
|
-
|
|
88
|
+
参考骨架:
|
|
52
89
|
|
|
53
90
|
```css
|
|
54
91
|
/* 整页表格页 */
|
|
@@ -89,7 +126,7 @@ version: 2.0.0
|
|
|
89
126
|
}
|
|
90
127
|
```
|
|
91
128
|
|
|
92
|
-
|
|
129
|
+
AntD Table 示例结构:
|
|
93
130
|
|
|
94
131
|
```javascript
|
|
95
132
|
h('div', { className: 'table-page' },
|
|
@@ -162,17 +199,6 @@ h(antd.Table, {
|
|
|
162
199
|
})
|
|
163
200
|
```
|
|
164
201
|
|
|
165
|
-
硬性验收:
|
|
166
|
-
|
|
167
|
-
- 最外层容器高度明确:整页用 `height:100vh`;有顶部导航时用 `calc(100vh - 顶部导航高度)`;嵌在已固定高度的工作区时用 `height:100%; min-height:0`。
|
|
168
|
-
- 布局必须是 `display:flex; flex-direction:column`。
|
|
169
|
-
- 顶部筛选区 / toolbar 若存在,使用自适应高度并 `flex-shrink:0`。
|
|
170
|
-
- 中间表格区域必须是 `flex:1; min-height:0; overflow:auto`。
|
|
171
|
-
- 分页区域必须在容器最底部,使用 `flex-shrink:0; margin-top:auto`。
|
|
172
|
-
- 禁止分页随数据内容高度下移;数据很多时只滚动 `.table-data`。
|
|
173
|
-
- 表格列多时使用 antd `scroll.x` 或内部横滚,禁止撑出整页横向滚动条;需要随时可横向滑动时,横向滚动条必须固定在分页上方。
|
|
174
|
-
- 不要同时让 `body`、页面容器、表格容器三层都滚动;最多保留表格数据区滚动。
|
|
175
|
-
|
|
176
202
|
### 文本与图标换行控制(强制)
|
|
177
203
|
|
|
178
204
|
按钮、标签、导航项、状态徽章等**小范围整体区域**内的文本和图标,**禁止自动换行**:
|
|
@@ -321,7 +347,7 @@ antd Modal:`width="min(90vw, 520px)"`;抽屉窄屏优先 `placement="bottom"
|
|
|
321
347
|
- 导航栏通常要同时考虑未登录 / 已登录 / 管理员三种状态,以及收起 / 展开状态;普通用户不显示管理后台入口,管理员额外显示管理后台入口。
|
|
322
348
|
- 管理端侧边栏通常基于现有内置侧边栏修改,新增业务管理路由优先追加或局部调整;删除系统内置页面入口前先确认影响。
|
|
323
349
|
- 业务页面内可以做局部二级导航或侧边栏,但必须考虑外部导航已存在且可单独配置,避免重复渲染全局导航。
|
|
324
|
-
-
|
|
350
|
+
- 系统内置页面通常不改;确需修改登录、设置、权限、用户、系统配置等页面时,先说明影响、验证方式和保留的管理员能力。
|
|
325
351
|
- 页面风格不要照搬管理侧内置页面;业务前台按业务用户和品牌语境设计,管理侧按操作效率和信息密度设计。
|
|
326
352
|
|
|
327
353
|
### 新增页面绑定(强制)
|
|
@@ -362,6 +388,51 @@ antd Modal:`width="min(90vw, 520px)"`;抽屉窄屏优先 `placement="bottom"
|
|
|
362
388
|
|
|
363
389
|
---
|
|
364
390
|
|
|
391
|
+
## 前端底座与组件库清单
|
|
392
|
+
|
|
393
|
+
### 运行形态
|
|
394
|
+
|
|
395
|
+
- **壳层前端**位于 DraftGo 基座的 `frontend/`,技术栈为 React 19 + Vite 8 + Tailwind CSS 4;这是维护壳层代码时使用的构建链路。
|
|
396
|
+
- **业务页面和导航 HTML**存储在数据库资源中,由壳层以 iframe 运行。它们必须是完整的原生 HTML 文档,不能写入 TSX、ESM import、npm 依赖或 Vite 构建产物。
|
|
397
|
+
- 数据库页面需要 React 时,只能使用下方内置的 React 18 UMD 资源和 `window.React` / `window.ReactDOM`。不要把壳层的 React 19 npm 依赖、外部 CDN 或其他 React 版本混入页面。
|
|
398
|
+
- 新建页面先确认是“壳层源码修改”还是“数据库 HTML 页面”。二者的组件、构建和资源加载方式不同,不能混用。
|
|
399
|
+
|
|
400
|
+
### 内置组件库清单
|
|
401
|
+
|
|
402
|
+
| 名称 | 版本 | 运行形态 | 本地资源 |
|
|
403
|
+
|---|---:|---|---|
|
|
404
|
+
| Ant Design | 5.29.3 | React 18 UMD,导出 `window.antd` | `/assets/antd/reset.css`、`/assets/react/react.min.js`、`/assets/react-dom/react-dom.min.js`、`/assets/dayjs/dayjs.min.js`、`/assets/antd/antd.min.js` |
|
|
405
|
+
| Basecoat UI | 1.0.2 | HTML + CSS/JS,导出 `window.basecoat` | `/assets/vendor/basecoat/basecoat.min.css`、`/assets/vendor/basecoat/basecoat.min.js` |
|
|
406
|
+
| Oat UI | 0.7.0 | Web Components,导出 `window.ot` | `/assets/vendor/oat/oat.min.css`、`/assets/vendor/oat/oat.min.js` |
|
|
407
|
+
|
|
408
|
+
此清单不规定组件库选择或场景对应关系;根据页面需求自行决定是否使用其中任一库。
|
|
409
|
+
|
|
410
|
+
### 主题适配与标准加载方式
|
|
411
|
+
|
|
412
|
+
基座提供两个适配资源:`/assets/adapters/draftgo-ui.js` 和 `/assets/adapters/draftgo-theme.css`。
|
|
413
|
+
|
|
414
|
+
- `draftgo-ui.js` 暴露 `window.DraftGoUI`,其中 `DraftGoUI.load(name)` 接受 `antd`、`basecoat`、`oat`,按资源清单加载对应库;加载 AntD 后会为 React 根节点接入 DraftGo 的主题 token。
|
|
415
|
+
- `draftgo-theme.css` 用于 Basecoat 和 Oat,将其语义变量映射到 DraftGo 的 `--dg-*` token。它必须在对应组件库 CSS **之后**加载。
|
|
416
|
+
- 页面手工引入 AntD 时,必须严格遵守下方的依赖顺序;不要附加第三方 AntD CSS,也不要使用 ESM/CDN 版本。
|
|
417
|
+
|
|
418
|
+
```html
|
|
419
|
+
<!-- Basecoat:组件 CSS 在前,DraftGo 主题适配层在后 -->
|
|
420
|
+
<link href="/assets/vendor/basecoat/basecoat.min.css" rel="stylesheet">
|
|
421
|
+
<link href="/assets/adapters/draftgo-theme.css" rel="stylesheet">
|
|
422
|
+
<script src="/assets/adapters/draftgo-ui.js"></script>
|
|
423
|
+
<script src="/assets/vendor/basecoat/basecoat.min.js" defer></script>
|
|
424
|
+
|
|
425
|
+
<!-- 或由适配器按清单加载:await DraftGoUI.load('basecoat') -->
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
```html
|
|
429
|
+
<!-- Oat:同样让主题适配层位于组件 CSS 之后 -->
|
|
430
|
+
<link href="/assets/vendor/oat/oat.min.css" rel="stylesheet">
|
|
431
|
+
<link href="/assets/adapters/draftgo-theme.css" rel="stylesheet">
|
|
432
|
+
<script src="/assets/adapters/draftgo-ui.js"></script>
|
|
433
|
+
<script src="/assets/vendor/oat/oat.min.js" defer></script>
|
|
434
|
+
```
|
|
435
|
+
|
|
365
436
|
## 本地静态资源清单
|
|
366
437
|
|
|
367
438
|
| 路径 | 说明 |
|
|
@@ -372,6 +443,10 @@ antd Modal:`width="min(90vw, 520px)"`;抽屉窄屏优先 `placement="bottom"
|
|
|
372
443
|
| `/assets/react-dom/react-dom.min.js` | ReactDOM 18 UMD → `window.ReactDOM` |
|
|
373
444
|
| `/assets/dayjs/dayjs.min.js` | dayjs → antd DatePicker 依赖 |
|
|
374
445
|
| `/assets/antd/antd.min.js` | Ant Design 5.29.3 UMD → `window.antd` |
|
|
446
|
+
| `/assets/adapters/draftgo-ui.js` | DraftGo UI 适配器;提供 `window.DraftGoUI`、组件库按需加载和 AntD 主题接入 |
|
|
447
|
+
| `/assets/adapters/draftgo-theme.css` | Basecoat / Oat 到 DraftGo `--dg-*` 主题 token 的 CSS 映射;应后于对应组件 CSS 加载 |
|
|
448
|
+
| `/assets/vendor/basecoat/basecoat.min.css` + `basecoat.min.js` | Basecoat UI 1.0.2,原生 HTML 组件库 |
|
|
449
|
+
| `/assets/vendor/oat/oat.min.css` + `oat.min.js` | Oat UI 0.7.0,Web Components 组件库 |
|
|
375
450
|
| `/assets/fontawesome/css/all.min.css` | FontAwesome 6 |
|
|
376
451
|
| `/assets/icons/{name}.svg` | 内置精选 SVG 图标库(kebab-case 命名) |
|
|
377
452
|
| `/assets/icons/manifest.json` | 图标库映射清单 |
|
|
@@ -384,6 +459,7 @@ antd Modal:`width="min(90vw, 520px)"`;抽屉窄屏优先 `placement="bottom"
|
|
|
384
459
|
| `/assets/vendor/prism/prism.min.js` | 轻量代码高亮(Prism.js) |
|
|
385
460
|
| `/assets/vendor/dompurify/purify.min.js` | HTML 净化,渲染用户输入前必须过一遍 |
|
|
386
461
|
| `/assets/vendor/html2canvas/html2canvas.min.js` | html2canvas 1.4.1,页面截图 / DOM 导出图片 |
|
|
462
|
+
| `/assets/draftgo-chat.js` | DraftGo Chat SDK 完整版,注册 `<dg-chat>` 与 `DraftGoChat` |
|
|
387
463
|
| `/assets/vendor/gsap/gsap.min.js` | GSAP 核心(先于其他 GSAP 插件加载) |
|
|
388
464
|
| `/assets/vendor/gsap/ScrollTrigger.min.js` | GSAP ScrollTrigger |
|
|
389
465
|
| `/assets/vendor/gsap/ScrollToPlugin.min.js` | GSAP ScrollTo |
|
|
@@ -417,9 +493,10 @@ AI 模型 / Provider 品牌图标可以使用 LobeHub 静态图标的 npmmirror
|
|
|
417
493
|
|
|
418
494
|
---
|
|
419
495
|
|
|
420
|
-
## Ant Design
|
|
496
|
+
## Ant Design 运行契约
|
|
497
|
+
|
|
498
|
+
Ant Design 5.29.3 已内置为 UMD 包(`window.antd`)。
|
|
421
499
|
|
|
422
|
-
Ant Design 5.29.3 已内置为 UMD 包(`window.antd`),可优先选择使用。
|
|
423
500
|
|
|
424
501
|
### ① 依赖加载顺序(缺一不可,顺序不可乱)
|
|
425
502
|
|
|
@@ -433,7 +510,7 @@ Ant Design 5.29.3 已内置为 UMD 包(`window.antd`),可优先选择使
|
|
|
433
510
|
|
|
434
511
|
> 资源版本固定为 5.29.3。只加载 `/assets/antd/reset.css` 和 UMD JS;组件样式由 UMD 运行时注入,禁止额外加载或引用 `/assets/antd/antd.min.css`,也禁止混用其他版本的 Ant Design CSS。
|
|
435
512
|
|
|
436
|
-
### ②
|
|
513
|
+
### ② DraftGo 主题接入
|
|
437
514
|
|
|
438
515
|
用 `antd.ConfigProvider` 包裹根组件,读取 `App.theme` 和 CSS 变量 `--dg-accent`,实现 dark/light 自动切换和主题色联动:
|
|
439
516
|
|
|
@@ -474,9 +551,9 @@ ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(
|
|
|
474
551
|
- 自建组件时,业务页才是状态样式的所有者;状态规则必须收敛到该组件的专有类或页面根容器内,不能影响其他组件或页面。
|
|
475
552
|
- 改动前先确认所有权;同一状态属性不得同时由 token/API 和 CSS 覆盖定义。若必须接管第三方组件样式,先移除或禁用原有对应效果,再用有作用域的规则完整接管。
|
|
476
553
|
|
|
477
|
-
### ④
|
|
554
|
+
### ④ DraftGo 平台反馈 API 对照
|
|
478
555
|
|
|
479
|
-
| antd 原生 |
|
|
556
|
+
| antd 原生 | 对应平台 API |
|
|
480
557
|
|-----------|------|
|
|
481
558
|
| `Modal.confirm(...)` | `await App.confirm(msg, title)` |
|
|
482
559
|
| `Modal.info / warning(...)` | `App.showModal(msg, title)` |
|
|
@@ -511,7 +588,7 @@ h('div', { style: { textAlign: 'center' } },
|
|
|
511
588
|
)
|
|
512
589
|
```
|
|
513
590
|
|
|
514
|
-
###
|
|
591
|
+
### antd 页面:`antd.Empty` 示例
|
|
515
592
|
|
|
516
593
|
```javascript
|
|
517
594
|
const { Empty } = antd;
|
|
@@ -564,7 +641,7 @@ h('div', { style: { display:'flex', flexDirection:'column', alignItems:'center',
|
|
|
564
641
|
|
|
565
642
|
## GSAP 动效规范
|
|
566
643
|
|
|
567
|
-
|
|
644
|
+
GSAP 是 DraftGo 内置动效库。使用时遵守以下加载和清理约束。
|
|
568
645
|
|
|
569
646
|
加载规则:
|
|
570
647
|
- 核心 `gsap.min.js` 必须首先加载,其余插件按需选加
|
|
@@ -697,18 +774,47 @@ App.setColorScheme('deep-blue-white');
|
|
|
697
774
|
|
|
698
775
|
## AIHub 页面 SDK
|
|
699
776
|
|
|
777
|
+
新页面需要 AI 对话 UI 时,加载完整版脚本并使用原生 `<dg-chat>`:
|
|
778
|
+
|
|
779
|
+
```html
|
|
780
|
+
<script src="/assets/draftgo-chat.js"></script>
|
|
781
|
+
<dg-chat protocol="draftgo-agent" agent-id="AGENT_ID"></dg-chat>
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
需要动态创建时使用 `DraftGoChat.create()`:
|
|
785
|
+
|
|
786
|
+
```javascript
|
|
787
|
+
const chat = DraftGoChat.create('#chat-host', {
|
|
788
|
+
protocol: 'draftgo-agent',
|
|
789
|
+
agentId,
|
|
790
|
+
surface: 'inline',
|
|
791
|
+
view: 'conversation'
|
|
792
|
+
});
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
完整配置、协议、事件、历史、扩展与安全约束见 `references/chat-sdk.md`。
|
|
796
|
+
|
|
797
|
+
`DraftGoAI` 是同一脚本提供的旧代码兼容门面,只用于没有对话 UI 的轻量调用或图片生成:
|
|
798
|
+
|
|
700
799
|
```javascript
|
|
701
|
-
//
|
|
800
|
+
// 聊天(默认流式;onDelta(delta, full) 每个增量触发,Promise resolve 完整文本)
|
|
702
801
|
const text = await DraftGoAI.chat(agentId, '你好', (delta, full) => render(full));
|
|
703
802
|
|
|
803
|
+
// 多轮:传 sessionId 复用会话(→ body.session_id);Agent 开启“持续对话”后服务端续写历史。不传 = 无状态单轮
|
|
804
|
+
await DraftGoAI.chat(agentId, '接着上一条', handler, { sessionId: threadKey });
|
|
805
|
+
|
|
704
806
|
// 图片生成(必须用 /images 接口,不要用 /chat)
|
|
705
807
|
const result = await DraftGoAI.images(agentId, '生成主图', { size: '1024x1024', n: 1 });
|
|
706
808
|
|
|
707
809
|
// 用户选模型
|
|
708
810
|
const { user_selectable, models } = await DraftGoAI.getSelectableModels(agentId);
|
|
709
|
-
await DraftGoAI.chat(agentId, '你好', handler, { model: selectedModel });
|
|
811
|
+
await DraftGoAI.chat(agentId, '你好', handler, { model: selectedModel, sessionId: threadKey });
|
|
710
812
|
```
|
|
711
813
|
|
|
814
|
+
- 使用 `DraftGoAI` 前同样必须先加载 `/assets/draftgo-chat.js`;壳层不会默认注入。
|
|
815
|
+
- `DraftGoAI.chat` 内部创建隐藏 `<dg-chat>`,始终流式。新页面的可见对话、附件、模型选择器、推理、历史和重生成直接使用 `<dg-chat>`。
|
|
816
|
+
- Agent 的能力与 `spec` 字段(工具/子智能体/记忆/结构化输出/多模态/持续对话/ttft failover)见 `references/aihub.md`。
|
|
817
|
+
|
|
712
818
|
---
|
|
713
819
|
|
|
714
820
|
## 退出登录
|
|
@@ -755,4 +861,4 @@ const dbRes = await App.get('db/order', params);
|
|
|
755
861
|
const q = (window.__DG_ROUTE_CONTEXT__ || window.__DG_GET_ROUTE_CONTEXT__?.() || App?.getCurrentRouteContext?.() || { query: {} }).query;
|
|
756
862
|
```
|
|
757
863
|
|
|
758
|
-
完整 API 表 → `{{SKILL_DIR}}/
|
|
864
|
+
完整 API 表 → `{{SKILL_DIR}}/references/app-api.md`
|
|
@@ -12,7 +12,7 @@ read_when: 评估功能可行性时 · 选择开发路径时 · 进入新项目
|
|
|
12
12
|
| 导航栏 | 数据库 HTML(`navigation.html`) | `.draftgo/navigations/` |
|
|
13
13
|
| 动态 DB | db_meta 定义 schema + `/api/db/{type}` 操作数据 | `.draftgo/db_meta/` |
|
|
14
14
|
| 自定义服务 | Go `Register` 服务,支持 route/event/scheduled 混合注册 | `.draftgo/custom_scripts/` |
|
|
15
|
-
| AIHub | 配置 AI Agent
|
|
15
|
+
| AIHub | 配置 AI Agent(模型/编排/能力/子智能体/记忆);页面对话使用 `<dg-chat>`,图片模式使用 `DraftGoAI.images`;见 `references/chat-sdk.md` 与 `references/aihub.md` | `.draftgo/aihub/` |
|
|
16
16
|
| 文档中心 | Markdown 文章 + 分类树 | `.draftgo/docs/articles/` |
|
|
17
17
|
| 系统配置 | KV 存储,含全局前端层槽位 | `.draftgo/system_config/` |
|
|
18
18
|
|
|
@@ -35,11 +35,13 @@ read_when: 评估功能可行性时 · 选择开发路径时 · 进入新项目
|
|
|
35
35
|
→ 仅需 KV → sys_config(category 自定义)
|
|
36
36
|
|
|
37
37
|
要调用 AI?
|
|
38
|
-
→ 聊天/问答 → AIHub +
|
|
38
|
+
→ 聊天/问答 UI → AIHub + `<dg-chat>`(自动隔离 thread/session)
|
|
39
|
+
→ 无 UI 的旧代码文本调用 → AIHub + DraftGoAI.chat()(兼容门面)
|
|
39
40
|
→ 图片生成 → AIHub + DraftGoAI.images()
|
|
41
|
+
→ 需要工具/子智能体/记忆/结构化输出/多模态 → 都是 Agent spec 开关,见 references/aihub.md
|
|
40
42
|
|
|
41
43
|
要调用第三方服务?
|
|
42
|
-
→ 自定义服务(用 `
|
|
44
|
+
→ 自定义服务(用 `draftgo.HTTP` 请求;需要时可用 route/event/scheduled 加工)
|
|
43
45
|
|
|
44
46
|
要展示内容文档?
|
|
45
47
|
→ 文档中心(Markdown + 分类树)
|
|
@@ -53,7 +55,7 @@ read_when: 评估功能可行性时 · 选择开发路径时 · 进入新项目
|
|
|
53
55
|
|
|
54
56
|
## 自定义服务边界
|
|
55
57
|
|
|
56
|
-
- 新服务使用 Go `Register(app *sdk.App)`
|
|
58
|
+
- 新服务使用 Go `Register(app *sdk.App)` 自动注册路由、事件和定时任务;完整语言、`draftgo` 和 SDK 契约见 `references/custom-services.md`。
|
|
57
59
|
- `route`:对外暴露 HTTP 端点,运行时路径为 `/api/x/{slug}/{path}`;同一 Go 服务可用多个 `app.Route(method, path, handler)` 注册多个端点,推送后要真实请求验证。
|
|
58
60
|
- `event`:响应平台事件,如 `db.created` / `db.updated` / `user.registered`;用 `app.On(event, handler)` 注册,可为同一事件注册多个 handler。
|
|
59
61
|
- `scheduled`:用 `app.Schedule("分 时 日 月 周", handler)` 注册 cron;同一服务可声明多个定时 handler。
|
|
@@ -61,4 +63,4 @@ read_when: 评估功能可行性时 · 选择开发路径时 · 进入新项目
|
|
|
61
63
|
- 管理面由角色 RBAC 的 `scripts:*` 动作控制;Route 调用面继续由每个服务的 `permission` / `route_security` 独立控制,不要把两层权限混为一谈。
|
|
62
64
|
- Go 服务在独立子进程中构建/运行;只有可信角色才能获得 `scripts:create` / `scripts:update`。
|
|
63
65
|
- 自定义服务适合服务端加工、鉴权后聚合、第三方回调、定时任务和事件响应;普通 CRUD 管理界面优先用“页面 + 动态 DB”,不要把所有业务后台都塞进 route 脚本。
|
|
64
|
-
- 脚本内读动态 DB 用 `
|
|
66
|
+
- 脚本内读动态 DB 用 `draftgo.DB.Query(type, sdk.QueryOptions{...})`;返回 `sdk.QueryResult`,分页和筛选见 `references/custom-services.md`。
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# DraftGo 并行执行
|
|
2
|
+
|
|
3
|
+
## 资源依赖
|
|
4
|
+
|
|
5
|
+
先为每个工作单元记录:
|
|
6
|
+
|
|
7
|
+
- `depends`:开始前必须完成的工作单元。
|
|
8
|
+
- `resource_lock`:本单元会修改的文件或 DraftGo 资源。
|
|
9
|
+
- `evidence`:完成后返回的验证证据。
|
|
10
|
+
|
|
11
|
+
按以下关系建立依赖:
|
|
12
|
+
|
|
13
|
+
| 资源关系 | 执行顺序 |
|
|
14
|
+
|---|---|
|
|
15
|
+
| 多个实现依赖同一 schema、API、权限或路由 | 先确定共享契约,再并行实现消费者。 |
|
|
16
|
+
| 两个工作单元修改同一文件或资源 | 合并为一个单元,或安排到连续 wave。 |
|
|
17
|
+
| 页面与对应导航入口 | 页面 route 确定后接入导航。 |
|
|
18
|
+
| 不同文件且没有数据或接口依赖 | 放入同一 wave 并行执行。 |
|
|
19
|
+
|
|
20
|
+
## 工作单元
|
|
21
|
+
|
|
22
|
+
分发内容包括:
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
objective: 要完成的结果
|
|
26
|
+
inputs: 当前资源和共享契约
|
|
27
|
+
resource_lock: [允许修改的文件或资源]
|
|
28
|
+
evidence: 返回的检查或运行证据
|
|
29
|
+
delivery_owner: main
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
工作单元返回修改结果、验证证据和阻塞信息。主代理维护共享契约、推进 wave、汇总资源并执行交付。
|
|
33
|
+
|
|
34
|
+
## Wave 执行
|
|
35
|
+
|
|
36
|
+
1. 确定 schema、服务端点、权限、route 和导航结构等共享契约。
|
|
37
|
+
2. 将 `depends` 已满足且 `resource_lock` 不冲突的工作单元放入同一 wave。
|
|
38
|
+
3. 并行执行当前 wave,汇总后回读所有变更资源。
|
|
39
|
+
4. 处理接口接线和入口绑定,再启动依赖已满足的下一 wave。
|
|
40
|
+
5. 统一运行 `draftgo check`、必要的 API/UI 验证,并按交付意图执行批量推送。
|
|
41
|
+
|
|
42
|
+
某个工作单元失败时,其依赖项等待处理;其余独立分支继续汇总。发现资源锁冲突时,将冲突单元合并或移到后续 wave。
|
|
43
|
+
|
|
44
|
+
批量交付示例:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
draftgo auto-push --batch pages 1,2,3 nav 4 custom_scripts 7
|
|
48
|
+
```
|
|
@@ -36,7 +36,7 @@ read_when: 需要理解运行时机制时 · 处理 token/路由/事件相关问
|
|
|
36
36
|
|
|
37
37
|
`app` 组成:`api.js`(请求)+ `feedback.js`(弹窗/Toast)+ `runtime.js`(路由/主题)+ `i18n.js`(国际化)+ state(currentUser/isAdmin/config/theme)
|
|
38
38
|
|
|
39
|
-
完整 API 见 `{{SKILL_DIR}}/
|
|
39
|
+
完整 API 见 `{{SKILL_DIR}}/references/app-api.md`
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
@@ -17,13 +17,14 @@ DraftGo Push Script
|
|
|
17
17
|
python draftgo_push.py roles [role_id ...]
|
|
18
18
|
python draftgo_push.py users [user_id ...]
|
|
19
19
|
"""
|
|
20
|
-
import json, sys, re
|
|
20
|
+
import json, sys, re, datetime
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
import urllib.request, urllib.error
|
|
23
23
|
|
|
24
24
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
25
25
|
PROBE_ROUTES = False
|
|
26
26
|
DRY_RUN = False
|
|
27
|
+
RUN_FAILURES = 0
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def find_project_root(start: Path) -> Path:
|
|
@@ -67,7 +68,9 @@ def _lessons_reminder(cfg):
|
|
|
67
68
|
print(" 未命中则跳过。")
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
def api_call(method, server, token, path, body=None):
|
|
71
|
+
def api_call(method, server, token, path, body=None, acceptable_errors=()):
|
|
72
|
+
"""发起 API 调用,并记录调用方不会恢复的失败。"""
|
|
73
|
+
global RUN_FAILURES
|
|
71
74
|
data = json.dumps(body, ensure_ascii=False).encode("utf-8") if body is not None else None
|
|
72
75
|
headers = {
|
|
73
76
|
"Authorization": f"Bearer {token}",
|
|
@@ -89,8 +92,12 @@ def api_call(method, server, token, path, body=None):
|
|
|
89
92
|
with urllib.request.urlopen(req, timeout=15) as r:
|
|
90
93
|
return True, r.status, r.read().decode("utf-8", errors="replace")
|
|
91
94
|
except urllib.error.HTTPError as e:
|
|
92
|
-
|
|
95
|
+
body_text = e.read().decode(errors="replace")
|
|
96
|
+
if e.code not in acceptable_errors:
|
|
97
|
+
RUN_FAILURES += 1
|
|
98
|
+
return False, e.code, body_text
|
|
93
99
|
except Exception as e:
|
|
100
|
+
RUN_FAILURES += 1
|
|
94
101
|
return False, None, str(e)
|
|
95
102
|
|
|
96
103
|
|
|
@@ -201,6 +208,57 @@ def _response_items(body):
|
|
|
201
208
|
return []
|
|
202
209
|
|
|
203
210
|
|
|
211
|
+
def _response_item(body):
|
|
212
|
+
"""解析单资源响应的统一信封。"""
|
|
213
|
+
try:
|
|
214
|
+
raw = json.loads(body)
|
|
215
|
+
except Exception:
|
|
216
|
+
return None
|
|
217
|
+
if isinstance(raw, dict) and "data" in raw:
|
|
218
|
+
raw = raw["data"]
|
|
219
|
+
return raw if isinstance(raw, dict) else None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _cloud_version_allows_push(server, token, path, local_item, label):
|
|
223
|
+
"""云端优先:本地 pull 基线落后于云端时禁止静默覆盖。
|
|
224
|
+
|
|
225
|
+
没有历史 updated_at 的旧缓存保持兼容;404 由调用方按默认重建策略处理。
|
|
226
|
+
"""
|
|
227
|
+
global RUN_FAILURES
|
|
228
|
+
baseline = local_item.get("updated_at")
|
|
229
|
+
if not baseline:
|
|
230
|
+
return True
|
|
231
|
+
ok, status, body = api_call("GET", server, token, path, acceptable_errors=(404,))
|
|
232
|
+
if not ok and status == 404:
|
|
233
|
+
return True
|
|
234
|
+
if not ok:
|
|
235
|
+
return False
|
|
236
|
+
remote = _response_item(body)
|
|
237
|
+
remote_updated = remote.get("updated_at") if remote else None
|
|
238
|
+
if remote_updated and str(remote_updated) != str(baseline):
|
|
239
|
+
RUN_FAILURES += 1
|
|
240
|
+
conflict_dir = DEFAULT_ROOT / ".draftgo" / "sync-conflicts"
|
|
241
|
+
conflict_dir.mkdir(parents=True, exist_ok=True)
|
|
242
|
+
identity = str(local_item.get("id") or label).replace("/", "_").replace("\\", "_")
|
|
243
|
+
stamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
244
|
+
conflict_path = conflict_dir / f"{identity}-{stamp}.json"
|
|
245
|
+
conflict_path.write_text(json.dumps({
|
|
246
|
+
"kind": "remote-newer",
|
|
247
|
+
"resource_path": path,
|
|
248
|
+
"baseline_updated_at": baseline,
|
|
249
|
+
"remote_updated_at": remote_updated,
|
|
250
|
+
"local": local_item,
|
|
251
|
+
"remote": remote,
|
|
252
|
+
}, ensure_ascii=False, indent=2, default=str), encoding="utf-8")
|
|
253
|
+
print(
|
|
254
|
+
f" ERR [{label}] 云端版本已变化(local={baseline}, remote={remote_updated});"
|
|
255
|
+
f"云端优先,已保存冲突快照:{conflict_path.relative_to(DEFAULT_ROOT)}。",
|
|
256
|
+
file=sys.stderr,
|
|
257
|
+
)
|
|
258
|
+
return False
|
|
259
|
+
return True
|
|
260
|
+
|
|
261
|
+
|
|
204
262
|
def _recover_missing_index_entries(
|
|
205
263
|
server, token, items, ids, id_key, type_name, rel_index, endpoint,
|
|
206
264
|
file_spec=None, drop_fields=(),
|
|
@@ -359,7 +417,9 @@ def sync_pages(server, token, ids=None):
|
|
|
359
417
|
"value": {"html": html},
|
|
360
418
|
}
|
|
361
419
|
if pid:
|
|
362
|
-
|
|
420
|
+
if not _cloud_version_allows_push(server, token, f"/api/pages/{pid}", page, title):
|
|
421
|
+
continue
|
|
422
|
+
ok, status, body = api_call("PUT", server, token, f"/api/pages/{pid}", payload, acceptable_errors=(404,))
|
|
363
423
|
# PUT 404:本地 id 与云端不一致(删了重建 / 跨环境),按 route 自动创建
|
|
364
424
|
if not ok and status == 404:
|
|
365
425
|
print(f" WARN [{title}] page_id={pid} 不存在,尝试创建")
|
|
@@ -406,7 +466,7 @@ def sync_db_meta(server, token, ids=None):
|
|
|
406
466
|
"extra": meta.get("extra"),
|
|
407
467
|
}
|
|
408
468
|
if mid:
|
|
409
|
-
ok, status, body = api_call("PUT", server, token, f"/api/db-meta/{mid}", payload)
|
|
469
|
+
ok, status, body = api_call("PUT", server, token, f"/api/db-meta/{mid}", payload, acceptable_errors=(404,))
|
|
410
470
|
# 本地 index 的 id 与云端不一致(删了重建 / 跨环境同步)时 PUT 404,按 type 创建
|
|
411
471
|
if not ok and status == 404:
|
|
412
472
|
print(f" WARN [{label}] db_meta_id={mid} 不存在,尝试按 type 创建")
|
|
@@ -451,7 +511,7 @@ def sync_nav(server, token, ids=None):
|
|
|
451
511
|
continue
|
|
452
512
|
html = html_file.read_text(encoding="utf-8")
|
|
453
513
|
if nid:
|
|
454
|
-
ok, status, body = api_call("PUT", server, token, f"/api/navigations/{nid}", {"html": html})
|
|
514
|
+
ok, status, body = api_call("PUT", server, token, f"/api/navigations/{nid}", {"html": html}, acceptable_errors=(404,))
|
|
455
515
|
if not ok and status == 404:
|
|
456
516
|
print(f" WARN [{name}] nav_id={nid} 不存在,尝试创建")
|
|
457
517
|
nid = None
|
|
@@ -502,7 +562,7 @@ def sync_aihub(server, token, ids=None):
|
|
|
502
562
|
"tags", "describe", "permission", "status",
|
|
503
563
|
) if it.get(k) is not None}
|
|
504
564
|
if iid:
|
|
505
|
-
ok, status, body = api_call("PUT", server, token, f"/api/aihub/{iid}", payload)
|
|
565
|
+
ok, status, body = api_call("PUT", server, token, f"/api/aihub/{iid}", payload, acceptable_errors=(404,))
|
|
506
566
|
if not ok and status == 404:
|
|
507
567
|
print(f" WARN [{name}] aihub_id={iid} 不存在,尝试创建")
|
|
508
568
|
iid = None
|
|
@@ -553,10 +613,10 @@ def sync_system_config(server, token, keys=None):
|
|
|
553
613
|
# 前端全局层属于系统默认配置,默认字段的描述/分类/状态由基座维护。
|
|
554
614
|
# 推送时只更新值,避免旧 index 中的元信息触发“系统默认字段不允许修改字段描述”。
|
|
555
615
|
payload = value_payload if _is_frontend_global_config(it) else meta_payload
|
|
556
|
-
ok, status, body = api_call("PUT", server, token, f"/api/system/{ck}", payload)
|
|
616
|
+
ok, status, body = api_call("PUT", server, token, f"/api/system/{ck}", payload, acceptable_errors=(404,))
|
|
557
617
|
if not ok and _is_protected_system_config_error(body) and payload != value_payload:
|
|
558
618
|
print(f" WARN [{ck}] 系统默认字段元信息受保护,改为仅推送 config_value")
|
|
559
|
-
ok, status, body = api_call("PUT", server, token, f"/api/system/{ck}", value_payload)
|
|
619
|
+
ok, status, body = api_call("PUT", server, token, f"/api/system/{ck}", value_payload, acceptable_errors=(404,))
|
|
560
620
|
if not ok and status == 404:
|
|
561
621
|
print(f" WARN [{ck}] system_config 不存在,尝试创建")
|
|
562
622
|
create_payload = _system_config_create_payload(ck, it, value)
|
|
@@ -675,7 +735,7 @@ def sync_docs(server, token, ids=None):
|
|
|
675
735
|
payload["content"] = content
|
|
676
736
|
payload["content_type"] = "html"
|
|
677
737
|
if aid:
|
|
678
|
-
ok, status, body = api_call("PUT", server, token, f"/api/docs/articles/{aid}", payload)
|
|
738
|
+
ok, status, body = api_call("PUT", server, token, f"/api/docs/articles/{aid}", payload, acceptable_errors=(404,))
|
|
679
739
|
if not ok and status == 404:
|
|
680
740
|
print(f" WARN [{title}] article_id={aid} 不存在,尝试创建")
|
|
681
741
|
aid = None
|
|
@@ -716,7 +776,7 @@ def sync_doc_categories(server, token, ids=None):
|
|
|
716
776
|
"name", "slug", "description", "icon", "parent_id", "sort_order", "status",
|
|
717
777
|
) if it.get(k) is not None}
|
|
718
778
|
if cid:
|
|
719
|
-
ok, status, body = api_call("PUT", server, token, f"/api/docs/categories/{cid}", payload)
|
|
779
|
+
ok, status, body = api_call("PUT", server, token, f"/api/docs/categories/{cid}", payload, acceptable_errors=(404,))
|
|
720
780
|
if not ok and status == 404:
|
|
721
781
|
print(f" WARN [{name}] category_id={cid} 不存在,尝试创建")
|
|
722
782
|
cid = None
|
|
@@ -783,12 +843,14 @@ def sync_custom_scripts(server, token, ids=None):
|
|
|
783
843
|
continue
|
|
784
844
|
code = code_path.read_text(encoding="utf-8")
|
|
785
845
|
if sid:
|
|
846
|
+
if not _cloud_version_allows_push(server, token, f"/api/scripts/{sid}", it, name):
|
|
847
|
+
continue
|
|
786
848
|
# ScriptUpdate 接受字段(不含 slug/mode,避免误改启停/路由)
|
|
787
849
|
payload = {k: it.get(k) for k in (
|
|
788
850
|
"name", "description", "config", "permission", "go_mod", "go_sum",
|
|
789
851
|
) if it.get(k) is not None}
|
|
790
852
|
payload["code"] = code
|
|
791
|
-
ok, status, body = api_call("PUT", server, token, f"/api/scripts/{sid}", payload)
|
|
853
|
+
ok, status, body = api_call("PUT", server, token, f"/api/scripts/{sid}", payload, acceptable_errors=(404,))
|
|
792
854
|
if not ok and status == 404:
|
|
793
855
|
print(f" WARN [{name}] script_id={sid} 不存在,尝试创建")
|
|
794
856
|
sid = None
|
|
@@ -938,6 +1000,9 @@ def main():
|
|
|
938
1000
|
server, token, cfg = load_config()
|
|
939
1001
|
run_batch(server, token, args[1:])
|
|
940
1002
|
_lessons_reminder(cfg)
|
|
1003
|
+
if RUN_FAILURES:
|
|
1004
|
+
print(f"ERR: push finished with {RUN_FAILURES} failed request(s)", file=sys.stderr)
|
|
1005
|
+
sys.exit(1)
|
|
941
1006
|
return
|
|
942
1007
|
|
|
943
1008
|
if args[0] not in HANDLERS:
|
|
@@ -948,6 +1013,9 @@ def main():
|
|
|
948
1013
|
server, token, cfg = load_config()
|
|
949
1014
|
HANDLERS[mode](server, token, ids)
|
|
950
1015
|
_lessons_reminder(cfg)
|
|
1016
|
+
if RUN_FAILURES:
|
|
1017
|
+
print(f"ERR: push finished with {RUN_FAILURES} failed request(s)", file=sys.stderr)
|
|
1018
|
+
sys.exit(1)
|
|
951
1019
|
|
|
952
1020
|
|
|
953
1021
|
if __name__ == "__main__":
|