lite-questionnaire 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +358 -0
- package/core.ts +477 -0
- package/design/questionnaire-openapi.yaml +522 -0
- package/index.ts +396 -0
- package/input.ts +513 -0
- package/modules/confirm.ts +42 -0
- package/modules/multiSelect.ts +100 -0
- package/modules/rating.ts +105 -0
- package/modules/select.ts +118 -0
- package/modules/shared.ts +10 -0
- package/modules/text.ts +71 -0
- package/package.json +39 -0
- package/render.ts +319 -0
- package/skills/lite-questionnaire/SKILL.md +276 -0
- package/state.ts +39 -0
- package/types.ts +137 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yunwuhai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# lite-questionnaire
|
|
2
|
+
|
|
3
|
+
通用交互式问卷工具,支持单选、多选、文本输入、确认、评分五种问题类型,提供条件子问题、约束校验、会话持久化等高级特性。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [参数与字段说明](#参数与字段说明)
|
|
8
|
+
- [基础字段(所有类型共用)](#基础字段所有类型共用)
|
|
9
|
+
- [select / multiSelect 特有字段](#select--multiselect-特有字段)
|
|
10
|
+
- [text 特有字段](#text-特有字段)
|
|
11
|
+
- [confirm 特有字段](#confirm-特有字段)
|
|
12
|
+
- [rating 特有字段](#rating-特有字段)
|
|
13
|
+
- [约束条件](#约束条件)
|
|
14
|
+
- [条件子问题](#条件子问题)
|
|
15
|
+
- [交互方式与按键表](#交互方式与按键表)
|
|
16
|
+
- [返回结果](#返回结果)
|
|
17
|
+
- [会话持久化](#会话持久化)
|
|
18
|
+
- [完整示例](#完整示例)
|
|
19
|
+
- [OpenAPI 规范](#openapi-规范)
|
|
20
|
+
- [代码架构](#代码架构)
|
|
21
|
+
|
|
22
|
+
## 问题类型
|
|
23
|
+
|
|
24
|
+
| 类型 | 标识 | 说明 | Enter 行为 |
|
|
25
|
+
|------|------|------|-----------|
|
|
26
|
+
| `select` | 单选 | Space 标定一项,Enter 提交 | 提交并前进 |
|
|
27
|
+
| `multiSelect` | 多选 | Space 切换勾选多项,Enter 批量提交 | 提交并前进 |
|
|
28
|
+
| `text` | 文本 | 按 Tab 进入内联编辑,无选项列表 | 提交并前进 |
|
|
29
|
+
| `confirm` | 确认 | Y/N 双按钮,← → 切换 | 提交默认值"是"并前进 |
|
|
30
|
+
| `rating` | 评分 | 滑块条 + 表情量表 + 文字注释 | 提交默认值 3 并前进 |
|
|
31
|
+
|
|
32
|
+
> **注意**:所有问题均为必答。`confirm` 和 `rating` 具有默认值(是 / 3),无需进入编辑态即可直接 Enter 提交。`text` 类型留空按 Enter 会显示错误提示,不可跳过。←/→ 切换问题时自动提交当前草稿(有值则保存,无值则标红)。
|
|
33
|
+
|
|
34
|
+
## 参数与字段说明
|
|
35
|
+
|
|
36
|
+
顶层参数:
|
|
37
|
+
|
|
38
|
+
| 参数 | 类型 | 说明 |
|
|
39
|
+
|------|------|------|
|
|
40
|
+
| `questions` | `Question[]` | 问题列表(至少 1 个) |
|
|
41
|
+
|
|
42
|
+
### 基础字段(所有类型共用)
|
|
43
|
+
|
|
44
|
+
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
|
45
|
+
|------|------|------|------|------|
|
|
46
|
+
| `id` | string | ✅ | - | 唯一标识,作为结果映射的键名 |
|
|
47
|
+
| `label` | string | ✅ | - | Tab 栏短标签 |
|
|
48
|
+
| `prompt` | string | ✅ | - | 完整问题文本 |
|
|
49
|
+
| `type` | enum | ✅ | - | select / multiSelect / text / confirm / rating |
|
|
50
|
+
| `constraints` | Constraint[] | ❌ | [] | 声明式约束校验(详见[约束条件](#约束条件)) |
|
|
51
|
+
| `children` | Question[] | ❌ | [] | 条件子问题(详见[条件子问题](#条件子问题)) |
|
|
52
|
+
| `showIf` | { value } | ❌ | - | 子问题条件,仅 `children` 中的子问题使用 |
|
|
53
|
+
|
|
54
|
+
<a id="select--multiselect-特有字段"></a>
|
|
55
|
+
|
|
56
|
+
### select / multiSelect 特有字段
|
|
57
|
+
|
|
58
|
+
| 字段 | 类型 | 说明 |
|
|
59
|
+
|------|------|------|
|
|
60
|
+
| `options` | Option[] | 选项列表 `{ value, label, description? }` |
|
|
61
|
+
| `maxSelect` | number | 单选 = 1,多选 >= 2(最大可选数量) |
|
|
62
|
+
|
|
63
|
+
> 末尾自动追加"自定义"选项,Space 勾选 + Tab 编辑自定义值。
|
|
64
|
+
|
|
65
|
+
### text 特有字段
|
|
66
|
+
|
|
67
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
68
|
+
|------|------|------|------|
|
|
69
|
+
| `placeholder` | string | - | 输入框占位符 |
|
|
70
|
+
| `multiline` | boolean | false | 是否启用多行文本编辑 |
|
|
71
|
+
|
|
72
|
+
### confirm 特有字段
|
|
73
|
+
|
|
74
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
75
|
+
|------|------|------|------|
|
|
76
|
+
| `yesLabel` | string | "是" | 确认按钮文本 |
|
|
77
|
+
| `noLabel` | string | "否" | 否认按钮文本 |
|
|
78
|
+
|
|
79
|
+
> 默认选中"是"。编辑态 ←/→ 切换按钮,Enter 提交。
|
|
80
|
+
|
|
81
|
+
### rating 特有字段
|
|
82
|
+
|
|
83
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
84
|
+
|------|------|------|------|
|
|
85
|
+
| `range` | { min: 1, max: 5 } | - | 固定 1-5 评分范围 |
|
|
86
|
+
| `showEmoji` | boolean | false | 显示表情量表(😡😟😐😊😍) |
|
|
87
|
+
| `annotations` | Record<string, string> | - | 数值到文字注释的映射 |
|
|
88
|
+
|
|
89
|
+
> 默认值为 3。编辑态 ←/→ 或数字键调整,Enter 提交。
|
|
90
|
+
|
|
91
|
+
## 约束条件
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"constraints": [
|
|
96
|
+
{ "type": "minSelect", "value": 1, "message": "至少选择一项" },
|
|
97
|
+
{ "type": "minLength", "value": 10, "message": "至少 10 个字符" }
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| type | 适用问题 | value | 说明 |
|
|
103
|
+
|------|---------|-------|------|
|
|
104
|
+
| `required` | 全部 | - | 必填校验 |
|
|
105
|
+
| `minSelect` | multiSelect | number | 最少选择项数 |
|
|
106
|
+
| `maxSelect` | multiSelect | number | 最多选择项数 |
|
|
107
|
+
| `minLength` | text | number | 最小文本长度 |
|
|
108
|
+
| `maxLength` | text | number | 最大文本长度 |
|
|
109
|
+
| `pattern` | text | string | 正则匹配 |
|
|
110
|
+
|
|
111
|
+
## 条件子问题
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"id": "lang",
|
|
116
|
+
"type": "select",
|
|
117
|
+
"maxSelect": 1,
|
|
118
|
+
"label": "语言",
|
|
119
|
+
"prompt": "用哪种编程语言?",
|
|
120
|
+
"options": [
|
|
121
|
+
{ "value": "ts", "label": "TypeScript" },
|
|
122
|
+
{ "value": "py", "label": "Python" }
|
|
123
|
+
],
|
|
124
|
+
"children": [
|
|
125
|
+
{
|
|
126
|
+
"id": "framework",
|
|
127
|
+
"type": "select",
|
|
128
|
+
"maxSelect": 1,
|
|
129
|
+
"label": "框架",
|
|
130
|
+
"prompt": "用哪个框架?",
|
|
131
|
+
"showIf": { "value": "ts" },
|
|
132
|
+
"options": [
|
|
133
|
+
{ "value": "react", "label": "React" },
|
|
134
|
+
{ "value": "vue", "label": "Vue" }
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
当用户选择 "TypeScript" 时,子问题"框架"自动插入 Tab 序列中(紧跟在父问题之后)。子问题也可以嵌套自己的 `children`。
|
|
142
|
+
|
|
143
|
+
## 交互方式与按键表
|
|
144
|
+
|
|
145
|
+
| 按键 | 单选 | 多选 | 文本 | 确认 | 评分 |
|
|
146
|
+
|------|------|------|------|------|------|
|
|
147
|
+
| ↑ ↓ | 导航选项 | 导航选项 | — | — | — |
|
|
148
|
+
| ← → | 切换问题 | 切换问题 | 切换问题 | 切换按钮(编辑态) | 调整滑块(编辑态) |
|
|
149
|
+
| Space | 标定选项 | 切换勾选 | — | — | — |
|
|
150
|
+
| Enter | 提交+前进 | 提交+前进 | 提交+前进 | 提交+前进 | 提交+前进 |
|
|
151
|
+
| Tab | 编辑自定义 | 编辑自定义 | 进入编辑 | 进入编辑 | 进入编辑 |
|
|
152
|
+
| 1-9 | 跳转选项 | 跳转选项 | — | — | 1-5 跳到对应值 |
|
|
153
|
+
| Esc | 取消问卷 | 取消问卷 | 取消问卷 | 取消问卷 | 取消问卷 |
|
|
154
|
+
|
|
155
|
+
| ← → 非编辑态 | 切换 Tab,有草稿/默认值时自动保存(进度点变绿),无值时标红 |
|
|
156
|
+
| ← → 编辑态 | 仅操作当前控件(confirm 切换按钮,rating 调整数值),不切换问题 |
|
|
157
|
+
|
|
158
|
+
- **进度点**(三态状态机):⚪ 未访问 → 🔴 已访问但无值 → 🟢 已完成
|
|
159
|
+
- **默认值**:`confirm` 默认"是"、`rating` 默认中间值,无需编辑即可直接 Enter 提交
|
|
160
|
+
- **←/→ 切题**:自动执行非强制提交。有有效值时保存变绿;空值离开标红;违反约束则阻止切换
|
|
161
|
+
- **← 回退**:随时回到上一题修改,草稿保留。修改后 Enter 覆盖原答案
|
|
162
|
+
- **提交页**:汇总所有答案,未完成项禁止提交,必须 ← 回去补答
|
|
163
|
+
- **自定义选项**:单选/多选选项列表末尾始终有"自定义",Space 勾选 + Tab 编辑独立操作
|
|
164
|
+
- **单题问卷**:完成后直接返回结果(跳过提交页)
|
|
165
|
+
|
|
166
|
+
## 返回结果
|
|
167
|
+
|
|
168
|
+
提交后返回 `QuestionnaireResult`,`answers` 为键值对,键为问题 `id`,值为对应答案结构。
|
|
169
|
+
|
|
170
|
+
### 不取消时(正常提交)
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"answers": {
|
|
175
|
+
"<问题id>": { ... },
|
|
176
|
+
"<问题id>": { ... }
|
|
177
|
+
},
|
|
178
|
+
"submittedAt": "2026-05-26T12:00:00.000Z"
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 取消时(按 Esc)
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"cancelled": true,
|
|
187
|
+
"message": "User cancelled the questionnaire"
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 答案类型
|
|
192
|
+
|
|
193
|
+
**SelectAnswer** — 单选
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{ "value": "ts", "label": "TypeScript", "wasCustom": false }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
| 字段 | 类型 | 说明 |
|
|
200
|
+
|------|------|------|
|
|
201
|
+
| `value` | string | 用户选择的选项值;自定义内容时为用户输入 |
|
|
202
|
+
| `label` | string | 用户选择的选项显示文本 |
|
|
203
|
+
| `wasCustom` | boolean? | 是否来自"自定义"选项(仅自定义时为 `true`) |
|
|
204
|
+
|
|
205
|
+
**MultiSelectAnswer** — 多选
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{ "values": ["auth","api"], "labels": ["用户认证","REST API"], "wasCustom": false }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
| 字段 | 类型 | 说明 |
|
|
212
|
+
|------|------|------|
|
|
213
|
+
| `values` | string[] | 用户选择的所有选项值 |
|
|
214
|
+
| `labels` | string[] | 用户选择的所有选项显示文本 |
|
|
215
|
+
| `wasCustom` | boolean? | 是否包含"自定义"选项(仅含自定义时为 `true`) |
|
|
216
|
+
|
|
217
|
+
**TextAnswer** — 文本
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{ "text": "一个高性能 API 服务" }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| 字段 | 类型 | 说明 |
|
|
224
|
+
|------|------|------|
|
|
225
|
+
| `text` | string | 用户输入的文本内容 |
|
|
226
|
+
|
|
227
|
+
**ConfirmAnswer** — 确认
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{ "confirmed": true, "label": "创建" }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
| 字段 | 类型 | 说明 |
|
|
234
|
+
|------|------|------|
|
|
235
|
+
| `confirmed` | boolean | 用户的选择:`true` = 是,`false` = 否 |
|
|
236
|
+
| `label` | string | 用户选中的按钮文本(`yesLabel` 或 `noLabel`) |
|
|
237
|
+
|
|
238
|
+
**RatingAnswer** — 评分
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{ "value": 5, "annotation": "非常好" }
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
| 字段 | 类型 | 说明 |
|
|
245
|
+
|------|------|------|
|
|
246
|
+
| `value` | number | 用户选择的评分值(整数) |
|
|
247
|
+
| `annotation` | string | 对应数值的文字注释;未配置注解时为空字符串 |
|
|
248
|
+
|
|
249
|
+
## 会话持久化
|
|
250
|
+
|
|
251
|
+
问卷状态自动跨 TUI 会话保存与恢复。如果 AI 多次调用 `questionnaire` 工具传相同的 `questions` 列表:
|
|
252
|
+
|
|
253
|
+
- 首次调用:初始化为空白问卷
|
|
254
|
+
- 后续调用:恢复上一次的答案和进度,用户可继续编辑或提交
|
|
255
|
+
- 按 Esc 取消后:下次调用仍恢复之前的草稿(取消不会丢失数据)
|
|
256
|
+
- 提交后:下一次相同 `questions` 的调用将重新开始(清空状态)
|
|
257
|
+
|
|
258
|
+
对 AI 而言无需特殊处理,正常调用 `questionnaire` 即可享受无缝续填体验。
|
|
259
|
+
|
|
260
|
+
## 完整示例
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"questions": [
|
|
265
|
+
{
|
|
266
|
+
"id": "lang",
|
|
267
|
+
"type": "select",
|
|
268
|
+
"maxSelect": 1,
|
|
269
|
+
"label": "语言",
|
|
270
|
+
"prompt": "用哪种编程语言?",
|
|
271
|
+
"options": [
|
|
272
|
+
{ "value": "ts", "label": "TypeScript" },
|
|
273
|
+
{ "value": "py", "label": "Python" },
|
|
274
|
+
{ "value": "rs", "label": "Rust" }
|
|
275
|
+
],
|
|
276
|
+
"children": [
|
|
277
|
+
{
|
|
278
|
+
"id": "framework",
|
|
279
|
+
"type": "select",
|
|
280
|
+
"maxSelect": 1,
|
|
281
|
+
"label": "框架",
|
|
282
|
+
"prompt": "用哪个框架?",
|
|
283
|
+
"showIf": { "value": "ts" },
|
|
284
|
+
"options": [
|
|
285
|
+
{ "value": "react", "label": "React" },
|
|
286
|
+
{ "value": "vue", "label": "Vue" }
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"id": "features",
|
|
293
|
+
"type": "multiSelect",
|
|
294
|
+
"maxSelect": 3,
|
|
295
|
+
"label": "功能",
|
|
296
|
+
"prompt": "需要哪些功能?",
|
|
297
|
+
"options": [
|
|
298
|
+
{ "value": "auth", "label": "用户认证" },
|
|
299
|
+
{ "value": "api", "label": "REST API" },
|
|
300
|
+
{ "value": "ws", "label": "WebSocket" }
|
|
301
|
+
],
|
|
302
|
+
"constraints": [
|
|
303
|
+
{ "type": "minSelect", "value": 1, "message": "至少选择一项" }
|
|
304
|
+
]
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"id": "description",
|
|
308
|
+
"type": "text",
|
|
309
|
+
"label": "描述",
|
|
310
|
+
"prompt": "项目描述",
|
|
311
|
+
"multiline": true
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"id": "confirm",
|
|
315
|
+
"type": "confirm",
|
|
316
|
+
"label": "确认",
|
|
317
|
+
"prompt": "确定要创建项目?",
|
|
318
|
+
"yesLabel": "创建",
|
|
319
|
+
"noLabel": "取消"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"id": "satisfaction",
|
|
323
|
+
"type": "rating",
|
|
324
|
+
"label": "满意度",
|
|
325
|
+
"prompt": "对代码质量的满意度?",
|
|
326
|
+
"range": { "min": 1, "max": 5 },
|
|
327
|
+
"showEmoji": true,
|
|
328
|
+
"annotations": { "1": "非常差", "5": "非常好" }
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## OpenAPI 规范
|
|
335
|
+
|
|
336
|
+
完整的 OpenAPI 3.0 类型定义见 [`design/questionnaire-openapi.yaml`](design/questionnaire-openapi.yaml)。包含所有 schema 定义、答案类型、约束类型和完整请求示例。
|
|
337
|
+
|
|
338
|
+
## 代码架构
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
lite-questionnaire/
|
|
342
|
+
├── index.ts # 入口,注册工具 + TypeBox Schema + 渲染回调
|
|
343
|
+
├── core.ts # 状态管理、子问题展开、状态机进度判定、约束校验
|
|
344
|
+
├── types.ts # TypeScript 类型定义
|
|
345
|
+
├── render.ts # 通用渲染(面板框架、Tab 栏、提交页、提示栏)
|
|
346
|
+
├── input.ts # 全局按键分发(← → Space Enter Esc Tab 1-9)
|
|
347
|
+
├── state.ts # 会话持久化(pi.appendEntry)
|
|
348
|
+
├── modules/
|
|
349
|
+
│ ├── shared.ts # 模块间共享类型
|
|
350
|
+
│ ├── select.ts # 单选渲染 + 输入处理
|
|
351
|
+
│ ├── multiSelect.ts # 多选渲染 + 输入处理
|
|
352
|
+
│ ├── text.ts # 文本编辑渲染 + 草稿保存
|
|
353
|
+
│ ├── confirm.ts # 确认双按钮渲染
|
|
354
|
+
│ └── rating.ts # 评分滑块 + 表情渲染
|
|
355
|
+
├── design/
|
|
356
|
+
│ └── questionnaire-openapi.yaml
|
|
357
|
+
└── README.md
|
|
358
|
+
```
|