@agimon-ai/doompi-user-feedback 0.0.1-alpha.34 → 0.0.1-alpha.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/package.json +9 -8
- package/src/exports/webClient.ts +1 -0
- package/{web → src/web}/QuestionnairePrompt.tsx +37 -3
- package/{web → src/web}/questionnaireDraft.ts +1 -1
- package/{web → src/web}/tsconfig.json +1 -1
- /package/{web → src/web}/AskUserQuestionToolMessage.tsx +0 -0
- /package/{web → src/web}/askUserText.ts +0 -0
- /package/{web → src/web}/index.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-user-feedback",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.35",
|
|
4
4
|
"description": "Structured user questions with interactive and autonomous Voice handoff for Pi agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
|
-
"web",
|
|
30
|
+
"src/web",
|
|
31
|
+
"src/exports/webClient.ts",
|
|
31
32
|
"src/types/askUserWire.ts",
|
|
32
33
|
"src/types/questionnaire.ts",
|
|
33
34
|
"README.md",
|
|
@@ -57,10 +58,10 @@
|
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"@deepseek-ai/cordis": "4.0.1",
|
|
59
60
|
"typebox": "1.3.19",
|
|
60
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
61
|
-
"@agimon-ai/doompi-
|
|
62
|
-
"@agimon-ai/doompi-web-
|
|
63
|
-
"@agimon-ai/doompi-
|
|
61
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.45",
|
|
62
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.46",
|
|
63
|
+
"@agimon-ai/doompi-web-components": "0.0.1-alpha.6",
|
|
64
|
+
"@agimon-ai/doompi-web-contracts": "0.0.1-alpha.9"
|
|
64
65
|
},
|
|
65
66
|
"devDependencies": {
|
|
66
67
|
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
"doompiWeb": {
|
|
94
95
|
"pluginId": "ask-user",
|
|
95
96
|
"channels": [],
|
|
96
|
-
"client": "./
|
|
97
|
+
"client": "./src/exports/webClient.ts"
|
|
97
98
|
},
|
|
98
99
|
"pi": {
|
|
99
100
|
"extensions": [
|
|
@@ -103,7 +104,7 @@
|
|
|
103
104
|
"scripts": {
|
|
104
105
|
"build": "tsdown",
|
|
105
106
|
"test": "vitest --run",
|
|
106
|
-
"typecheck": "tsc --noEmit && tsc --noEmit -p web/tsconfig.json",
|
|
107
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p src/web/tsconfig.json",
|
|
107
108
|
"lint": "oxlint . && oxfmt . --check",
|
|
108
109
|
"fixcode": "oxlint . --fix && oxfmt ."
|
|
109
110
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { webPlugin } from '../web/index.ts';
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
Textarea,
|
|
11
11
|
} from '@agimon-ai/doompi-web-components';
|
|
12
12
|
import type { ToolPromptRenderProps } from '@agimon-ai/doompi-web-contracts';
|
|
13
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
14
|
-
import { encodeAnswerEnvelope } from '../
|
|
13
|
+
import { type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react';
|
|
14
|
+
import { encodeAnswerEnvelope } from '../types/askUserWire.ts';
|
|
15
15
|
import {
|
|
16
16
|
chooseOption,
|
|
17
17
|
CUSTOM_LABEL,
|
|
@@ -70,6 +70,23 @@ function StepBar({
|
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The frame's shortcuts stop at a text field: while the caret is in one the
|
|
75
|
+
* arrows and Enter belong to the field, so they are kept from bubbling. Enter
|
|
76
|
+
* commits the field the way the terminal editor does, Shift+Enter writes a
|
|
77
|
+
* newline, and Escape and Cmd/Ctrl+Enter still reach the frame.
|
|
78
|
+
*/
|
|
79
|
+
function fieldKeys(commit: () => void) {
|
|
80
|
+
return (event: KeyboardEvent<HTMLTextAreaElement>): void => {
|
|
81
|
+
if (event.key === 'Escape') return;
|
|
82
|
+
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) return;
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
if (event.key !== 'Enter' || event.shiftKey) return;
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
commit();
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
/** One question's options, its typed-answer row, and the preview of whatever is focused. */
|
|
74
91
|
function QuestionBody({
|
|
75
92
|
question,
|
|
@@ -78,6 +95,7 @@ function QuestionBody({
|
|
|
78
95
|
cursor,
|
|
79
96
|
onCursor,
|
|
80
97
|
onDraft,
|
|
98
|
+
onCommit,
|
|
81
99
|
}: {
|
|
82
100
|
question: PromptQuestion;
|
|
83
101
|
index: number;
|
|
@@ -86,6 +104,8 @@ function QuestionBody({
|
|
|
86
104
|
onCursor: (cursor: number) => void;
|
|
87
105
|
/** `settles` is true only for an edit that finishes the question, which is what may move on. */
|
|
88
106
|
onDraft: (draft: QuestionnaireDraft, settles: boolean) => void;
|
|
107
|
+
/** Enter in a text field: hands the keyboard back to the frame, settling the question or not. */
|
|
108
|
+
onCommit: (settles: boolean) => void;
|
|
89
109
|
}) {
|
|
90
110
|
const entry = draftEntry(draft, index);
|
|
91
111
|
const typing = entry.custom !== null;
|
|
@@ -152,6 +172,7 @@ function QuestionBody({
|
|
|
152
172
|
value={entry.custom ?? ''}
|
|
153
173
|
placeholder="type your answer…"
|
|
154
174
|
onChange={(event) => onDraft(setCustom(draft, index, event.target.value), false)}
|
|
175
|
+
onKeyDown={fieldKeys(() => onCommit(true))}
|
|
155
176
|
className="text-[12px]"
|
|
156
177
|
/>
|
|
157
178
|
</div>
|
|
@@ -174,6 +195,7 @@ function QuestionBody({
|
|
|
174
195
|
value={entry.notes}
|
|
175
196
|
placeholder="anything the options do not cover"
|
|
176
197
|
onChange={(event) => onDraft(setNotes(draft, index, event.target.value), false)}
|
|
198
|
+
onKeyDown={fieldKeys(() => onCommit(false))}
|
|
177
199
|
className="text-[11px]"
|
|
178
200
|
/>
|
|
179
201
|
</div>
|
|
@@ -199,6 +221,9 @@ export function QuestionnairePrompt({ args, dialog, answer, cancel }: ToolPrompt
|
|
|
199
221
|
|
|
200
222
|
// A new request is a new questionnaire; nothing from the last one carries over.
|
|
201
223
|
useEffect(() => {
|
|
224
|
+
// Remounting on dialog.id would be the alternative, but the prompt host owns the
|
|
225
|
+
// element, so the reset lives here.
|
|
226
|
+
// oxlint-disable-next-line react/set-state-in-effect
|
|
202
227
|
setDraft(emptyDraft(questions.length));
|
|
203
228
|
setCurrent(0);
|
|
204
229
|
setCursor(0);
|
|
@@ -262,6 +287,11 @@ export function QuestionnairePrompt({ args, dialog, answer, cancel }: ToolPrompt
|
|
|
262
287
|
cancel();
|
|
263
288
|
return;
|
|
264
289
|
}
|
|
290
|
+
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
submit();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
265
295
|
// While typing an answer the arrows belong to the textarea.
|
|
266
296
|
if (typing) return;
|
|
267
297
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
@@ -279,7 +309,7 @@ export function QuestionnairePrompt({ args, dialog, answer, cancel }: ToolPrompt
|
|
|
279
309
|
if (event.key === 'Enter') {
|
|
280
310
|
event.preventDefault();
|
|
281
311
|
const option = question.options[cursor];
|
|
282
|
-
if (
|
|
312
|
+
if (option === undefined) {
|
|
283
313
|
submit();
|
|
284
314
|
return;
|
|
285
315
|
}
|
|
@@ -305,6 +335,10 @@ export function QuestionnairePrompt({ args, dialog, answer, cancel }: ToolPrompt
|
|
|
305
335
|
cursor={cursor}
|
|
306
336
|
onCursor={setCursor}
|
|
307
337
|
onDraft={(next, settles) => move(next, current, settles)}
|
|
338
|
+
onCommit={(settles) => {
|
|
339
|
+
frame.current?.focus();
|
|
340
|
+
move(draft, current, settles);
|
|
341
|
+
}}
|
|
308
342
|
/>
|
|
309
343
|
</div>
|
|
310
344
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* A step the reader goes back to still has what they chose, and the agent is
|
|
11
11
|
* asked once rather than once per question.
|
|
12
12
|
*/
|
|
13
|
-
import type { QuestionAnswer } from '../
|
|
13
|
+
import type { QuestionAnswer } from '../types/questionnaire.ts';
|
|
14
14
|
|
|
15
15
|
/** The row every question gets, so a reader always has a way past a bad set of options. */
|
|
16
16
|
export const CUSTOM_LABEL = 'Type something.';
|
|
File without changes
|
|
File without changes
|
|
File without changes
|