@subako-ai/assistant-ui 0.2.2 → 0.2.4
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/dist/chat.d.ts +9 -2
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +29 -4
- package/dist/chat.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/input-form.d.ts +32 -0
- package/dist/input-form.d.ts.map +1 -0
- package/dist/input-form.js +101 -0
- package/dist/input-form.js.map +1 -0
- package/dist/runtime.d.ts +3 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/package.json +5 -5
- package/src/chat.tsx +46 -3
- package/src/index.ts +7 -0
- package/src/input-form.tsx +258 -0
- package/src/runtime.ts +3 -1
package/dist/chat.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
|
|
2
2
|
import { MessagePrimitive } from "@assistant-ui/react";
|
|
3
3
|
import type { ComponentProps, ReactNode } from "react";
|
|
4
|
+
import { type WidgetRenderers } from "./input-form.js";
|
|
4
5
|
import type { SubakoSession } from "./runtime.js";
|
|
5
6
|
/** What the approval prompt reads off a tool-call part: the call's name, its approval, and the answer. */
|
|
6
7
|
export type SubakoToolApprovalProps = Pick<ToolCallMessagePartProps, "toolName" | "approval" | "respondToApproval">;
|
|
@@ -33,17 +34,23 @@ export interface SubakoChatProps {
|
|
|
33
34
|
* unstyled; a page with no such policy needs nothing here.
|
|
34
35
|
*/
|
|
35
36
|
readonly nonce?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The custom widgets this app draws in a form the agent puts through
|
|
39
|
+
* `builtin:ask_user`, by widget id. A widget with no renderer here lets the
|
|
40
|
+
* person tell the agent the app cannot show it.
|
|
41
|
+
*/
|
|
42
|
+
readonly widgets?: WidgetRenderers;
|
|
36
43
|
/** Rendered inside the runtime, which is where a tool UI registers itself. */
|
|
37
44
|
readonly children?: ReactNode;
|
|
38
45
|
}
|
|
39
46
|
/**
|
|
40
47
|
* One session as a working chat: the transcript, the tool calls with their
|
|
41
|
-
* approvals, a composer, and a stop button while a run is under way. The
|
|
48
|
+
* approvals, the forms the agent puts to the person, a composer, and a stop button while a run is under way. The
|
|
42
49
|
* session is the app's own, so the chat opens no second connection; until it
|
|
43
50
|
* has one the thread is empty and the composer is disabled. It exists for the
|
|
44
51
|
* first ten minutes; an app that wants its own thread keeps `useSubakoRuntime`
|
|
45
52
|
* and builds the rest from assistant-ui's primitives.
|
|
46
53
|
*/
|
|
47
|
-
export declare function SubakoChat({ session, className, components, nonce, children }: SubakoChatProps): ReactNode;
|
|
54
|
+
export declare function SubakoChat({ session, className, components, nonce, widgets, children }: SubakoChatProps): ReactNode;
|
|
48
55
|
export {};
|
|
49
56
|
//# sourceMappingURL=chat.d.ts.map
|
package/dist/chat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA6B,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAC/F,OAAO,EAIN,gBAAgB,EAEhB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA6B,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAC/F,OAAO,EAIN,gBAAgB,EAEhB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAiMlD,0GAA0G;AAC1G,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,wBAAwB,EAAE,UAAU,GAAG,UAAU,GAAG,mBAAmB,CAAC,CAAC;AAEpH;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,EAAE,uBAAuB,GAAG,SAAS,CAehH;AA8CD,0GAA0G;AAC1G,KAAK,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;AA0C/F,MAAM,WAAW,eAAe;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,GAAG,SAAS,CAiCnH"}
|
package/dist/chat.js
CHANGED
|
@@ -5,6 +5,7 @@ import { MarkdownTextPrimitive } from "@assistant-ui/react-markdown";
|
|
|
5
5
|
import { useSessionState } from "@subako-ai/react";
|
|
6
6
|
import { createContext, useContext } from "react";
|
|
7
7
|
import remarkGfm from "remark-gfm";
|
|
8
|
+
import { SubakoInputForm } from "./input-form.js";
|
|
8
9
|
import { useSubakoRuntime } from "./runtime.js";
|
|
9
10
|
/**
|
|
10
11
|
* Enough style to read a conversation with no setup at all. Every surface names
|
|
@@ -89,6 +90,23 @@ const STYLE = `
|
|
|
89
90
|
border-radius: 6px; background: var(--subako-surface); color: var(--subako-fg); }
|
|
90
91
|
.subako-approval button { padding: 2px 10px; border: 1px solid var(--subako-border); border-radius: 6px;
|
|
91
92
|
background: var(--subako-bg); color: var(--subako-fg); font: inherit; cursor: pointer; }
|
|
93
|
+
.subako-inputs { display: flex; flex-direction: column; gap: 8px; padding: 0 8px; }
|
|
94
|
+
.subako-input-form { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 8px 10px;
|
|
95
|
+
border: 1px solid var(--subako-border); border-radius: 8px; background: var(--subako-surface);
|
|
96
|
+
color: var(--subako-fg); }
|
|
97
|
+
.subako-input-question { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; border: 0;
|
|
98
|
+
min-width: 0; }
|
|
99
|
+
.subako-input-prompt { padding: 0; font-weight: 600; }
|
|
100
|
+
.subako-input-options { display: flex; flex-direction: column; gap: 2px; }
|
|
101
|
+
.subako-input-option { display: flex; align-items: baseline; gap: 6px; }
|
|
102
|
+
.subako-input-hint { color: var(--subako-muted); font-size: 12px; }
|
|
103
|
+
.subako-input-text { padding: 4px 6px; border: 1px solid var(--subako-border); border-radius: 6px;
|
|
104
|
+
background: var(--subako-bg); color: var(--subako-fg); font: inherit; }
|
|
105
|
+
.subako-input-actions { display: flex; gap: 8px; }
|
|
106
|
+
.subako-input-actions button { padding: 2px 12px; border: 1px solid var(--subako-border); border-radius: 6px;
|
|
107
|
+
background: var(--subako-bg); color: var(--subako-fg); font: inherit; cursor: pointer; }
|
|
108
|
+
.subako-input-actions button:disabled { opacity: 0.5; cursor: default; }
|
|
109
|
+
.subako-input-error { color: var(--subako-error); font-size: 12px; }
|
|
92
110
|
.subako-composer { display: flex; gap: 8px; padding: 8px; border-top: 1px solid var(--subako-border);
|
|
93
111
|
background: var(--subako-bg); color: var(--subako-fg); }
|
|
94
112
|
.subako-input { flex: 1 1 auto; resize: none; padding: 6px 8px; border: 1px solid var(--subako-border);
|
|
@@ -111,7 +129,14 @@ div:where(.subako-chat) details:where(.subako-tool-fold),
|
|
|
111
129
|
div:where(.subako-chat) summary:where(.subako-tool-summary),
|
|
112
130
|
div:where(.subako-chat) span:where(.subako-tool-name, .subako-tool-status, .subako-writing),
|
|
113
131
|
div:where(.subako-chat) pre:where(.subako-tool-args, .subako-tool-result),
|
|
114
|
-
div:where(.subako-chat) :where(.subako-approval, .subako-composer) > button,
|
|
132
|
+
div:where(.subako-chat) :where(.subako-approval, .subako-composer, .subako-input-actions) > button,
|
|
133
|
+
div:where(.subako-chat) div:where(.subako-inputs, .subako-input-options, .subako-input-hint, .subako-input-actions,
|
|
134
|
+
.subako-input-error),
|
|
135
|
+
div:where(.subako-chat) :where(.subako-input-form):is(form, div),
|
|
136
|
+
div:where(.subako-chat) fieldset:where(.subako-input-question),
|
|
137
|
+
div:where(.subako-chat) legend:where(.subako-input-prompt),
|
|
138
|
+
div:where(.subako-chat) label:where(.subako-input-option),
|
|
139
|
+
div:where(.subako-chat) :where(.subako-input-text):is(input, textarea),
|
|
115
140
|
div:where(.subako-chat) form:where(.subako-composer),
|
|
116
141
|
div:where(.subako-chat) textarea:where(.subako-input),
|
|
117
142
|
div:where(.subako-chat) textarea:where(.subako-input)::placeholder { all: revert-layer; }
|
|
@@ -213,15 +238,15 @@ function AssistantMessage() {
|
|
|
213
238
|
}
|
|
214
239
|
/**
|
|
215
240
|
* One session as a working chat: the transcript, the tool calls with their
|
|
216
|
-
* approvals, a composer, and a stop button while a run is under way. The
|
|
241
|
+
* approvals, the forms the agent puts to the person, a composer, and a stop button while a run is under way. The
|
|
217
242
|
* session is the app's own, so the chat opens no second connection; until it
|
|
218
243
|
* has one the thread is empty and the composer is disabled. It exists for the
|
|
219
244
|
* first ten minutes; an app that wants its own thread keeps `useSubakoRuntime`
|
|
220
245
|
* and builds the rest from assistant-ui's primitives.
|
|
221
246
|
*/
|
|
222
|
-
export function SubakoChat({ session, className, components, nonce, children }) {
|
|
247
|
+
export function SubakoChat({ session, className, components, nonce, widgets, children }) {
|
|
223
248
|
const runtime = useSubakoRuntime(session);
|
|
224
249
|
const state = useSessionState(session);
|
|
225
|
-
return (_jsxs(AssistantRuntimeProvider, { runtime: runtime, children: [_jsx("style", { nonce: nonce, children: STYLE }), children, _jsx(PartsContext.Provider, { value: components, children: _jsxs(ThreadPrimitive.Root, { className: className === undefined ? "subako-chat" : `subako-chat ${className}`, children: [_jsx(ThreadPrimitive.Viewport, { className: "subako-viewport", children: _jsx(ThreadPrimitive.Messages, { components: { UserMessage, AssistantMessage } }) }), _jsxs(ComposerPrimitive.Root, { className: "subako-composer", children: [_jsx(ComposerPrimitive.Input, { className: "subako-input", rows: 1, placeholder: "Message the agent" }), _jsx(ComposerPrimitive.Send, { children: "Send" }), state?.isRunning === true ? _jsx(ComposerPrimitive.Cancel, { children: "Stop" }) : null] })] }) })] }));
|
|
250
|
+
return (_jsxs(AssistantRuntimeProvider, { runtime: runtime, children: [_jsx("style", { nonce: nonce, children: STYLE }), children, _jsx(PartsContext.Provider, { value: components, children: _jsxs(ThreadPrimitive.Root, { className: className === undefined ? "subako-chat" : `subako-chat ${className}`, children: [_jsx(ThreadPrimitive.Viewport, { className: "subako-viewport", children: _jsx(ThreadPrimitive.Messages, { components: { UserMessage, AssistantMessage } }) }), session === null || state === null || state.inputs.length === 0 ? null : (_jsx("div", { className: "subako-inputs", children: state.inputs.map((input) => (_jsx(SubakoInputForm, { input: input, widgets: widgets, onAnswer: (answer) => session.answerInput(input.call_id, answer) }, `${input.call_id}:${input.seq}`))) })), _jsxs(ComposerPrimitive.Root, { className: "subako-composer", children: [_jsx(ComposerPrimitive.Input, { className: "subako-input", rows: 1, placeholder: "Message the agent" }), _jsx(ComposerPrimitive.Send, { children: "Send" }), state?.isRunning === true ? _jsx(ComposerPrimitive.Cancel, { children: "Stop" }) : null] })] }) })] }));
|
|
226
251
|
}
|
|
227
252
|
//# sourceMappingURL=chat.js.map
|
package/dist/chat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EACN,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,GACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,SAAS,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SubakoChat, type SubakoChatProps, SubakoToolApproval, type SubakoToolApprovalProps } from "./chat.js";
|
|
2
|
+
export { SubakoInputForm, type SubakoInputFormProps, type WidgetProps, type WidgetRenderer, type WidgetRenderers, } from "./input-form.js";
|
|
2
3
|
export { convertLiveMessage, convertMessage } from "./convert.js";
|
|
3
4
|
export { type SubakoSession, useSubakoRuntime } from "./runtime.js";
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAC/G,OAAO,EACN,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,kBAAkB,EAAgC,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,kBAAkB,EAAgC,MAAM,WAAW,CAAC;AAC/G,OAAO,EACN,eAAe,GAKf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { InputAnswer, PendingInput, QuestionBody } from "@subako-ai/sdk";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
/** What a custom widget's renderer is handed: the agent's props, and the answer so far. */
|
|
4
|
+
export interface WidgetProps {
|
|
5
|
+
/** What the agent filled in, as the widget's props schema describes it. */
|
|
6
|
+
readonly props: unknown;
|
|
7
|
+
/** The answer so far: `undefined` until the person gives one. */
|
|
8
|
+
readonly value: unknown;
|
|
9
|
+
/** Sets the answer, which must match the widget's answer schema; `undefined` clears it. */
|
|
10
|
+
readonly onChange: (value: unknown) => void;
|
|
11
|
+
readonly question: QuestionBody;
|
|
12
|
+
}
|
|
13
|
+
/** A renderer for one custom widget, registered under the widget's id. */
|
|
14
|
+
export type WidgetRenderer = (props: WidgetProps) => ReactNode;
|
|
15
|
+
/** The custom widgets a client draws, by widget id. An id with no renderer answers `unsupported`. */
|
|
16
|
+
export type WidgetRenderers = Readonly<Record<string, WidgetRenderer>>;
|
|
17
|
+
export interface SubakoInputFormProps {
|
|
18
|
+
readonly input: PendingInput;
|
|
19
|
+
readonly widgets?: WidgetRenderers;
|
|
20
|
+
/** Sends the answer: `session.answerInput(input.call_id, answer)`. */
|
|
21
|
+
readonly onAnswer: (answer: InputAnswer) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One form an agent put through `builtin:ask_user`: each question drawn with
|
|
25
|
+
* its widget, a Submit that sends one outcome per question, and a Dismiss.
|
|
26
|
+
* A custom widget draws with the renderer `widgets` registers under its id; a
|
|
27
|
+
* question this client cannot draw is sent as `unsupported`, so the agent can
|
|
28
|
+
* ask it another way. A form none of whose questions can be drawn shows only
|
|
29
|
+
* a button that tells the agent so.
|
|
30
|
+
*/
|
|
31
|
+
export declare function SubakoInputForm({ input, widgets, onAnswer }: SubakoInputFormProps): ReactNode;
|
|
32
|
+
//# sourceMappingURL=input-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-form.d.ts","sourceRoot":"","sources":["../src/input-form.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAmC,MAAM,gBAAgB,CAAC;AAE/G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;CAChC;AAED,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS,CAAC;AAE/D,qGAAqG;AACrG,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;AAEvE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D;AA2ID;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,OAAY,EAAE,QAAQ,EAAE,EAAE,oBAAoB,GAAG,SAAS,CAiFlG"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { readWidget, submittedAnswer, unsupportedAnswer, widgetName } from "@subako-ai/sdk";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
/** Whether this client can draw `widget`. */
|
|
6
|
+
const drawable = (widget, widgets) => {
|
|
7
|
+
switch (widget.type) {
|
|
8
|
+
case "select":
|
|
9
|
+
case "text":
|
|
10
|
+
case "confirm":
|
|
11
|
+
return true;
|
|
12
|
+
case "custom":
|
|
13
|
+
return widgets[widget.data.id] !== undefined;
|
|
14
|
+
case "unsupported":
|
|
15
|
+
return false;
|
|
16
|
+
default:
|
|
17
|
+
return widget;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/** Whether `value` is an answer at all, rather than a blank. */
|
|
21
|
+
const given = (value) => value !== undefined && value !== "" && !(Array.isArray(value) && value.length === 0);
|
|
22
|
+
/** The question's outcome on submit: drawn and answered, left blank, or not drawable here. */
|
|
23
|
+
function outcomeOf(canDraw, value) {
|
|
24
|
+
if (!canDraw) {
|
|
25
|
+
return { type: "unsupported" };
|
|
26
|
+
}
|
|
27
|
+
return given(value) ? { type: "answered", data: { value } } : { type: "skipped" };
|
|
28
|
+
}
|
|
29
|
+
/** A select: radios for one pick, checkboxes for several, and a text box for the person's own answer. */
|
|
30
|
+
function SelectField({ question, widget, value, onChange, }) {
|
|
31
|
+
const picked = Array.isArray(value) ? value : [];
|
|
32
|
+
const labels = widget.data.options.map((option) => option.label);
|
|
33
|
+
const other = picked.find((label) => !labels.includes(label)) ?? "";
|
|
34
|
+
const chosen = picked.filter((label) => labels.includes(label));
|
|
35
|
+
const set = (next, otherText) => onChange(otherText === "" ? [...next] : [...next, otherText]);
|
|
36
|
+
return (_jsxs("div", { className: "subako-input-options", role: widget.data.multiple ? "group" : "radiogroup", children: [widget.data.options.map((option) => (_jsxs("label", { className: "subako-input-option", children: [_jsx("input", { type: widget.data.multiple ? "checkbox" : "radio", name: question.id, checked: chosen.includes(option.label), onChange: (event) => {
|
|
37
|
+
if (widget.data.multiple) {
|
|
38
|
+
set(event.target.checked ? [...chosen, option.label] : chosen.filter((label) => label !== option.label), other);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
set([option.label], "");
|
|
42
|
+
}
|
|
43
|
+
} }), _jsx("span", { children: option.label }), option.description === undefined ? null : _jsx("span", { className: "subako-input-hint", children: option.description })] }, option.label))), widget.data.allow_other ? (_jsx("input", { className: "subako-input-text", "aria-label": "Other", placeholder: "Other", value: other, onChange: (event) => set(widget.data.multiple ? chosen : [], event.target.value) })) : null] }));
|
|
44
|
+
}
|
|
45
|
+
function Field(props) {
|
|
46
|
+
const { question, widget, widgets, value, onChange } = props;
|
|
47
|
+
switch (widget.type) {
|
|
48
|
+
case "select":
|
|
49
|
+
return _jsx(SelectField, { ...props, widget: widget });
|
|
50
|
+
case "text":
|
|
51
|
+
return widget.data.multiline ? (_jsx("textarea", { className: "subako-input-text", "aria-label": question.prompt, rows: 3, value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.target.value) })) : (_jsx("input", { className: "subako-input-text", "aria-label": question.prompt, value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.target.value) }));
|
|
52
|
+
case "confirm":
|
|
53
|
+
return (_jsx("div", { className: "subako-input-options", role: "radiogroup", children: [true, false].map((answer) => (_jsxs("label", { className: "subako-input-option", children: [_jsx("input", { type: "radio", name: question.id, checked: value === answer, onChange: () => onChange(answer) }), _jsx("span", { children: answer ? "Yes" : "No" })] }, String(answer)))) }));
|
|
54
|
+
case "custom": {
|
|
55
|
+
const Render = widgets[widget.data.id];
|
|
56
|
+
return Render === undefined ? (_jsx(Unsupported, { widget: widget })) : (_jsx(Render, { props: widget.data.props, value: value, onChange: onChange, question: question }));
|
|
57
|
+
}
|
|
58
|
+
case "unsupported":
|
|
59
|
+
return _jsx(Unsupported, { widget: widget });
|
|
60
|
+
default:
|
|
61
|
+
return widget;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function Unsupported({ widget }) {
|
|
65
|
+
return _jsxs("div", { className: "subako-input-hint", children: ["This app can\u2019t show \u201C", widgetName(widget), "\u201D."] });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* One form an agent put through `builtin:ask_user`: each question drawn with
|
|
69
|
+
* its widget, a Submit that sends one outcome per question, and a Dismiss.
|
|
70
|
+
* A custom widget draws with the renderer `widgets` registers under its id; a
|
|
71
|
+
* question this client cannot draw is sent as `unsupported`, so the agent can
|
|
72
|
+
* ask it another way. A form none of whose questions can be drawn shows only
|
|
73
|
+
* a button that tells the agent so.
|
|
74
|
+
*/
|
|
75
|
+
export function SubakoInputForm({ input, widgets = {}, onAnswer }) {
|
|
76
|
+
const [values, setValues] = useState({});
|
|
77
|
+
const [sending, setSending] = useState(false);
|
|
78
|
+
const [error, setError] = useState(undefined);
|
|
79
|
+
const questions = input.request.questions.map((question) => {
|
|
80
|
+
const widget = readWidget(question.widget);
|
|
81
|
+
return { question, widget, canDraw: drawable(widget, widgets) };
|
|
82
|
+
});
|
|
83
|
+
const send = (answer) => {
|
|
84
|
+
setSending(true);
|
|
85
|
+
setError(undefined);
|
|
86
|
+
onAnswer(answer).then(() => setSending(false), (cause) => {
|
|
87
|
+
setSending(false);
|
|
88
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
const failure = error === undefined ? null : _jsx("div", { className: "subako-input-error", children: error });
|
|
92
|
+
if (questions.every(({ canDraw }) => !canDraw)) {
|
|
93
|
+
return (_jsxs("div", { className: "subako-input-form", "data-testid": `input-${input.call_id}`, children: [_jsx("div", { className: "subako-input-hint", children: "This app can\u2019t show the agent\u2019s question." }), _jsx("div", { className: "subako-input-actions", children: _jsx("button", { type: "button", disabled: sending, onClick: () => send(unsupportedAnswer(input)), children: "Tell the agent" }) }), failure] }));
|
|
94
|
+
}
|
|
95
|
+
const missing = questions.some(({ question, canDraw }) => question.required && canDraw && !given(values[question.id]));
|
|
96
|
+
return (_jsxs("form", { className: "subako-input-form", "data-testid": `input-${input.call_id}`, onSubmit: (event) => {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
send(submittedAnswer(input, Object.fromEntries(questions.map(({ question, canDraw }) => [question.id, outcomeOf(canDraw, values[question.id])]))));
|
|
99
|
+
}, children: [questions.map(({ question, widget }) => (_jsxs("fieldset", { className: "subako-input-question", children: [_jsxs("legend", { className: "subako-input-prompt", children: [question.header === undefined ? null : _jsxs("strong", { children: [question.header, ": "] }), question.prompt, question.required ? " *" : null] }), _jsx(Field, { question: question, widget: widget, widgets: widgets, value: values[question.id], onChange: (value) => setValues((previous) => ({ ...previous, [question.id]: value })) })] }, question.id))), _jsxs("div", { className: "subako-input-actions", children: [_jsx("button", { type: "submit", disabled: sending || missing, children: "Submit" }), _jsx("button", { type: "button", disabled: sending, onClick: () => send({ type: "declined", data: {} }), children: "Dismiss" })] }), failure] }));
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=input-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-form.js","sourceRoot":"","sources":["../src/input-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5F,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AA0BjC,6CAA6C;AAC7C,MAAM,QAAQ,GAAG,CAAC,MAAkB,EAAE,OAAwB,EAAW,EAAE;IAC1E,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,QAAQ,CAAC;QACd,KAAK,MAAM,CAAC;QACZ,KAAK,SAAS;YACb,OAAO,IAAI,CAAC;QACb,KAAK,QAAQ;YACZ,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,SAAS,CAAC;QAC9C,KAAK,aAAa;YACjB,OAAO,KAAK,CAAC;QACd;YACC,OAAO,MAAsB,CAAC;IAChC,CAAC;AACF,CAAC,CAAC;AAEF,gEAAgE;AAChE,MAAM,KAAK,GAAG,CAAC,KAAc,EAAW,EAAE,CACzC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;AAEtF,8FAA8F;AAC9F,SAAS,SAAS,CAAC,OAAgB,EAAE,KAAc;IAClD,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AACnF,CAAC;AAUD,yGAAyG;AACzG,SAAS,WAAW,CAAC,EACpB,QAAQ,EACR,MAAM,EACN,KAAK,EACL,QAAQ,GACmE;IAC3E,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAChE,MAAM,GAAG,GAAG,CAAC,IAAuB,EAAE,SAAiB,EAAE,EAAE,CAC1D,QAAQ,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IAC/D,OAAO,CACN,eAAK,SAAS,EAAC,sBAAsB,EAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,aACvF,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CACpC,iBAA0B,SAAS,EAAC,qBAAqB,aACxD,gBACC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,EACjD,IAAI,EAAE,QAAQ,CAAC,EAAE,EACjB,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EACtC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;4BACnB,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gCAC1B,GAAG,CACF,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,EACnG,KAAK,CACL,CAAC;4BACH,CAAC;iCAAM,CAAC;gCACP,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;4BACzB,CAAC;wBACF,CAAC,GACA,EACF,yBAAO,MAAM,CAAC,KAAK,GAAQ,EAC1B,MAAM,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,mBAAmB,YAAE,MAAM,CAAC,WAAW,GAAQ,KAjB/F,MAAM,CAAC,KAAK,CAkBhB,CACR,CAAC,EACD,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAC1B,gBACC,SAAS,EAAC,mBAAmB,gBAClB,OAAO,EAClB,WAAW,EAAC,OAAO,EACnB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAC/E,CACF,CAAC,CAAC,CAAC,IAAI,IACH,CACN,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,KAAiB;IAC/B,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAC7D,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,QAAQ;YACZ,OAAO,KAAC,WAAW,OAAK,KAAK,EAAE,MAAM,EAAE,MAAM,GAAI,CAAC;QACnD,KAAK,MAAM;YACV,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAC9B,mBACC,SAAS,EAAC,mBAAmB,gBACjB,QAAQ,CAAC,MAAM,EAC3B,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAChD,CACF,CAAC,CAAC,CAAC,CACH,gBACC,SAAS,EAAC,mBAAmB,gBACjB,QAAQ,CAAC,MAAM,EAC3B,KAAK,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAChD,CACF,CAAC;QACH,KAAK,SAAS;YACb,OAAO,CACN,cAAK,SAAS,EAAC,sBAAsB,EAAC,IAAI,EAAC,YAAY,YACrD,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAC9B,iBAA4B,SAAS,EAAC,qBAAqB,aAC1D,gBAAO,IAAI,EAAC,OAAO,EAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,KAAK,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAI,EACtG,yBAAO,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAQ,KAFzB,MAAM,CAAC,MAAM,CAAC,CAGlB,CACR,CAAC,GACG,CACN,CAAC;QACH,KAAK,QAAQ,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvC,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAC7B,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,GAAI,CAC/B,CAAC,CAAC,CAAC,CACH,KAAC,MAAM,IAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAC1F,CAAC;QACH,CAAC;QACD,KAAK,aAAa;YACjB,OAAO,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,GAAI,CAAC;QACxC;YACC,OAAO,MAAsB,CAAC;IAChC,CAAC;AACF,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,MAAM,EAAmC;IAC/D,OAAO,eAAK,SAAS,EAAC,mBAAmB,gDAAuB,UAAU,CAAC,MAAM,CAAC,eAAS,CAAC;AAC7F,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,EAAwB;IACtF,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAoC,EAAE,CAAC,CAAC;IAC5E,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAqB,SAAS,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC1D,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACjE,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,MAAmB,EAAE,EAAE;QACpC,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpB,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CACpB,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EACvB,CAAC,KAAc,EAAE,EAAE;YAClB,UAAU,CAAC,KAAK,CAAC,CAAC;YAClB,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAClE,CAAC,CACD,CAAC;IACH,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAK,SAAS,EAAC,oBAAoB,YAAE,KAAK,GAAO,CAAC;IAE/F,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QAChD,OAAO,CACN,eAAK,SAAS,EAAC,mBAAmB,iBAAc,SAAS,KAAK,CAAC,OAAO,EAAE,aACvE,cAAK,SAAS,EAAC,mBAAmB,oEAAgD,EAClF,cAAK,SAAS,EAAC,sBAAsB,YACpC,iBAAQ,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,+BAE7E,GACJ,EACL,OAAO,IACH,CACN,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAC7B,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CACtF,CAAC;IACF,OAAO,CACN,gBACC,SAAS,EAAC,mBAAmB,iBAChB,SAAS,KAAK,CAAC,OAAO,EAAE,EACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YACnB,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,IAAI,CACH,eAAe,CACd,KAAK,EACL,MAAM,CAAC,WAAW,CACjB,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAChG,CACD,CACD,CAAC;QACH,CAAC,aAEA,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CACxC,oBAA4B,SAAS,EAAC,uBAAuB,aAC5D,kBAAQ,SAAS,EAAC,qBAAqB,aACrC,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,6BAAS,QAAQ,CAAC,MAAM,UAAY,EAC3E,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IACxB,EACT,KAAC,KAAK,IACL,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,GACpF,KAZY,QAAQ,CAAC,EAAE,CAaf,CACX,CAAC,EACF,eAAK,SAAS,EAAC,sBAAsB,aACpC,iBAAQ,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,IAAI,OAAO,uBAEzC,EACT,iBAAQ,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,wBAEnF,IACJ,EACL,OAAO,IACF,CACP,CAAC;AACH,CAAC"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AssistantRuntime } from "@assistant-ui/react";
|
|
2
|
-
import type { SessionState } from "@subako-ai/sdk";
|
|
2
|
+
import type { InputAnswer, SessionState } from "@subako-ai/sdk";
|
|
3
3
|
/**
|
|
4
4
|
* What the runtime reads off a session: the SDK's `SessionConnection`, or
|
|
5
5
|
* anything else that carries the same members. It is spelled structurally so a
|
|
@@ -15,6 +15,8 @@ export interface SubakoSession {
|
|
|
15
15
|
/** Cancels the active run. */
|
|
16
16
|
cancel(): Promise<void>;
|
|
17
17
|
resolveApproval(callId: string, decision: "allow" | "deny", message?: string): Promise<void>;
|
|
18
|
+
/** Answers a form the agent put through `builtin:ask_user`. */
|
|
19
|
+
answerInput(callId: string, answer: InputAnswer): Promise<void>;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* An assistant-ui runtime over one Subako session: the log is the store, and
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG3E,OAAO,KAAK,EAAe,YAAY,EAAqB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG3E,OAAO,KAAK,EAAE,WAAW,EAAe,YAAY,EAAqB,MAAM,gBAAgB,CAAC;AAIhG;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC5E,sEAAsE;IACtE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,8BAA8B;IAC9B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7F,+DAA+D;IAC/D,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,gBAAgB,CA+BhF"}
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAwBlE,iGAAiG;AACjG,MAAM,OAAO,GAA2B,EAAE,CAAC;AAE3C,MAAM,YAAY,GAAG,CAAC,KAAkB,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AAErH,2EAA2E;AAC3E,SAAS,YAAY,CAAC,OAAsB;IAC3C,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC7D,kFAAkF;IAClF,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,EAAE,UAAU,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC;IACzB,yFAAyF;IACzF,MAAM,QAAQ,GAAG,OAAO,CACvB,GAA2B,EAAE,CAC5B,UAAU,KAAK,SAAS;QACvB,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YACxC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC,EAC7B,CAAC,UAAU,EAAE,IAAI,CAAC,CAClB,CAAC;IACF,OAAO,uBAAuB,CAAc;QAC3C,QAAQ;QACR,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,KAAK;QACpC,UAAU,EAAE,OAAO,KAAK,IAAI;QAC5B,cAAc,EAAE,YAAY;QAC5B,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,MAAM,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,QAAQ,EAAE,KAAK,IAAI,EAAE;YACpB,MAAM,OAAO,EAAE,MAAM,EAAE,CAAC;QACzB,CAAC;QACD,gFAAgF;QAChF,uBAAuB,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;YACnE,MAAM,OAAO,EAAE,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjF,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subako-ai/assistant-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "assistant-ui runtime and chat for Subako sessions",
|
|
5
5
|
"homepage": "https://subako.ai",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
"react": "^19.2.0",
|
|
40
40
|
"react-dom": "^19.2.0",
|
|
41
41
|
"typescript": "^7",
|
|
42
|
-
"@subako-ai/sdk": "0.2.
|
|
43
|
-
"@subako-ai/react": "0.2.
|
|
42
|
+
"@subako-ai/sdk": "0.2.4",
|
|
43
|
+
"@subako-ai/react": "0.2.4"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@assistant-ui/react": "^0.15.0",
|
|
47
47
|
"@assistant-ui/react-markdown": "^0.14.15",
|
|
48
48
|
"react": "^19.0.0",
|
|
49
|
-
"@subako-ai/react": "^0.2.
|
|
50
|
-
"@subako-ai/sdk": "^0.2.
|
|
49
|
+
"@subako-ai/react": "^0.2.4",
|
|
50
|
+
"@subako-ai/sdk": "^0.2.4"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=22"
|
package/src/chat.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import { useSessionState } from "@subako-ai/react";
|
|
|
13
13
|
import type { ComponentProps, ReactNode } from "react";
|
|
14
14
|
import { createContext, useContext } from "react";
|
|
15
15
|
import remarkGfm from "remark-gfm";
|
|
16
|
+
import { SubakoInputForm, type WidgetRenderers } from "./input-form.js";
|
|
16
17
|
import type { SubakoSession } from "./runtime.js";
|
|
17
18
|
import { useSubakoRuntime } from "./runtime.js";
|
|
18
19
|
|
|
@@ -99,6 +100,23 @@ const STYLE = `
|
|
|
99
100
|
border-radius: 6px; background: var(--subako-surface); color: var(--subako-fg); }
|
|
100
101
|
.subako-approval button { padding: 2px 10px; border: 1px solid var(--subako-border); border-radius: 6px;
|
|
101
102
|
background: var(--subako-bg); color: var(--subako-fg); font: inherit; cursor: pointer; }
|
|
103
|
+
.subako-inputs { display: flex; flex-direction: column; gap: 8px; padding: 0 8px; }
|
|
104
|
+
.subako-input-form { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 8px 10px;
|
|
105
|
+
border: 1px solid var(--subako-border); border-radius: 8px; background: var(--subako-surface);
|
|
106
|
+
color: var(--subako-fg); }
|
|
107
|
+
.subako-input-question { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; border: 0;
|
|
108
|
+
min-width: 0; }
|
|
109
|
+
.subako-input-prompt { padding: 0; font-weight: 600; }
|
|
110
|
+
.subako-input-options { display: flex; flex-direction: column; gap: 2px; }
|
|
111
|
+
.subako-input-option { display: flex; align-items: baseline; gap: 6px; }
|
|
112
|
+
.subako-input-hint { color: var(--subako-muted); font-size: 12px; }
|
|
113
|
+
.subako-input-text { padding: 4px 6px; border: 1px solid var(--subako-border); border-radius: 6px;
|
|
114
|
+
background: var(--subako-bg); color: var(--subako-fg); font: inherit; }
|
|
115
|
+
.subako-input-actions { display: flex; gap: 8px; }
|
|
116
|
+
.subako-input-actions button { padding: 2px 12px; border: 1px solid var(--subako-border); border-radius: 6px;
|
|
117
|
+
background: var(--subako-bg); color: var(--subako-fg); font: inherit; cursor: pointer; }
|
|
118
|
+
.subako-input-actions button:disabled { opacity: 0.5; cursor: default; }
|
|
119
|
+
.subako-input-error { color: var(--subako-error); font-size: 12px; }
|
|
102
120
|
.subako-composer { display: flex; gap: 8px; padding: 8px; border-top: 1px solid var(--subako-border);
|
|
103
121
|
background: var(--subako-bg); color: var(--subako-fg); }
|
|
104
122
|
.subako-input { flex: 1 1 auto; resize: none; padding: 6px 8px; border: 1px solid var(--subako-border);
|
|
@@ -121,7 +139,14 @@ div:where(.subako-chat) details:where(.subako-tool-fold),
|
|
|
121
139
|
div:where(.subako-chat) summary:where(.subako-tool-summary),
|
|
122
140
|
div:where(.subako-chat) span:where(.subako-tool-name, .subako-tool-status, .subako-writing),
|
|
123
141
|
div:where(.subako-chat) pre:where(.subako-tool-args, .subako-tool-result),
|
|
124
|
-
div:where(.subako-chat) :where(.subako-approval, .subako-composer) > button,
|
|
142
|
+
div:where(.subako-chat) :where(.subako-approval, .subako-composer, .subako-input-actions) > button,
|
|
143
|
+
div:where(.subako-chat) div:where(.subako-inputs, .subako-input-options, .subako-input-hint, .subako-input-actions,
|
|
144
|
+
.subako-input-error),
|
|
145
|
+
div:where(.subako-chat) :where(.subako-input-form):is(form, div),
|
|
146
|
+
div:where(.subako-chat) fieldset:where(.subako-input-question),
|
|
147
|
+
div:where(.subako-chat) legend:where(.subako-input-prompt),
|
|
148
|
+
div:where(.subako-chat) label:where(.subako-input-option),
|
|
149
|
+
div:where(.subako-chat) :where(.subako-input-text):is(input, textarea),
|
|
125
150
|
div:where(.subako-chat) form:where(.subako-composer),
|
|
126
151
|
div:where(.subako-chat) textarea:where(.subako-input),
|
|
127
152
|
div:where(.subako-chat) textarea:where(.subako-input)::placeholder { all: revert-layer; }
|
|
@@ -315,19 +340,25 @@ export interface SubakoChatProps {
|
|
|
315
340
|
* unstyled; a page with no such policy needs nothing here.
|
|
316
341
|
*/
|
|
317
342
|
readonly nonce?: string;
|
|
343
|
+
/**
|
|
344
|
+
* The custom widgets this app draws in a form the agent puts through
|
|
345
|
+
* `builtin:ask_user`, by widget id. A widget with no renderer here lets the
|
|
346
|
+
* person tell the agent the app cannot show it.
|
|
347
|
+
*/
|
|
348
|
+
readonly widgets?: WidgetRenderers;
|
|
318
349
|
/** Rendered inside the runtime, which is where a tool UI registers itself. */
|
|
319
350
|
readonly children?: ReactNode;
|
|
320
351
|
}
|
|
321
352
|
|
|
322
353
|
/**
|
|
323
354
|
* One session as a working chat: the transcript, the tool calls with their
|
|
324
|
-
* approvals, a composer, and a stop button while a run is under way. The
|
|
355
|
+
* approvals, the forms the agent puts to the person, a composer, and a stop button while a run is under way. The
|
|
325
356
|
* session is the app's own, so the chat opens no second connection; until it
|
|
326
357
|
* has one the thread is empty and the composer is disabled. It exists for the
|
|
327
358
|
* first ten minutes; an app that wants its own thread keeps `useSubakoRuntime`
|
|
328
359
|
* and builds the rest from assistant-ui's primitives.
|
|
329
360
|
*/
|
|
330
|
-
export function SubakoChat({ session, className, components, nonce, children }: SubakoChatProps): ReactNode {
|
|
361
|
+
export function SubakoChat({ session, className, components, nonce, widgets, children }: SubakoChatProps): ReactNode {
|
|
331
362
|
const runtime = useSubakoRuntime(session);
|
|
332
363
|
const state = useSessionState(session);
|
|
333
364
|
return (
|
|
@@ -339,6 +370,18 @@ export function SubakoChat({ session, className, components, nonce, children }:
|
|
|
339
370
|
<ThreadPrimitive.Viewport className="subako-viewport">
|
|
340
371
|
<ThreadPrimitive.Messages components={{ UserMessage, AssistantMessage }} />
|
|
341
372
|
</ThreadPrimitive.Viewport>
|
|
373
|
+
{session === null || state === null || state.inputs.length === 0 ? null : (
|
|
374
|
+
<div className="subako-inputs">
|
|
375
|
+
{state.inputs.map((input) => (
|
|
376
|
+
<SubakoInputForm
|
|
377
|
+
key={`${input.call_id}:${input.seq}`}
|
|
378
|
+
input={input}
|
|
379
|
+
widgets={widgets}
|
|
380
|
+
onAnswer={(answer) => session.answerInput(input.call_id, answer)}
|
|
381
|
+
/>
|
|
382
|
+
))}
|
|
383
|
+
</div>
|
|
384
|
+
)}
|
|
342
385
|
<ComposerPrimitive.Root className="subako-composer">
|
|
343
386
|
<ComposerPrimitive.Input className="subako-input" rows={1} placeholder="Message the agent" />
|
|
344
387
|
<ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export { SubakoChat, type SubakoChatProps, SubakoToolApproval, type SubakoToolApprovalProps } from "./chat.js";
|
|
2
|
+
export {
|
|
3
|
+
SubakoInputForm,
|
|
4
|
+
type SubakoInputFormProps,
|
|
5
|
+
type WidgetProps,
|
|
6
|
+
type WidgetRenderer,
|
|
7
|
+
type WidgetRenderers,
|
|
8
|
+
} from "./input-form.js";
|
|
2
9
|
export { convertLiveMessage, convertMessage } from "./convert.js";
|
|
3
10
|
export { type SubakoSession, useSubakoRuntime } from "./runtime.js";
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { InputAnswer, PendingInput, QuestionBody, QuestionOutcomeBody, ReadWidget } from "@subako-ai/sdk";
|
|
4
|
+
import { readWidget, submittedAnswer, unsupportedAnswer, widgetName } from "@subako-ai/sdk";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
|
|
8
|
+
/** What a custom widget's renderer is handed: the agent's props, and the answer so far. */
|
|
9
|
+
export interface WidgetProps {
|
|
10
|
+
/** What the agent filled in, as the widget's props schema describes it. */
|
|
11
|
+
readonly props: unknown;
|
|
12
|
+
/** The answer so far: `undefined` until the person gives one. */
|
|
13
|
+
readonly value: unknown;
|
|
14
|
+
/** Sets the answer, which must match the widget's answer schema; `undefined` clears it. */
|
|
15
|
+
readonly onChange: (value: unknown) => void;
|
|
16
|
+
readonly question: QuestionBody;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A renderer for one custom widget, registered under the widget's id. */
|
|
20
|
+
export type WidgetRenderer = (props: WidgetProps) => ReactNode;
|
|
21
|
+
|
|
22
|
+
/** The custom widgets a client draws, by widget id. An id with no renderer answers `unsupported`. */
|
|
23
|
+
export type WidgetRenderers = Readonly<Record<string, WidgetRenderer>>;
|
|
24
|
+
|
|
25
|
+
export interface SubakoInputFormProps {
|
|
26
|
+
readonly input: PendingInput;
|
|
27
|
+
readonly widgets?: WidgetRenderers;
|
|
28
|
+
/** Sends the answer: `session.answerInput(input.call_id, answer)`. */
|
|
29
|
+
readonly onAnswer: (answer: InputAnswer) => Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Whether this client can draw `widget`. */
|
|
33
|
+
const drawable = (widget: ReadWidget, widgets: WidgetRenderers): boolean => {
|
|
34
|
+
switch (widget.type) {
|
|
35
|
+
case "select":
|
|
36
|
+
case "text":
|
|
37
|
+
case "confirm":
|
|
38
|
+
return true;
|
|
39
|
+
case "custom":
|
|
40
|
+
return widgets[widget.data.id] !== undefined;
|
|
41
|
+
case "unsupported":
|
|
42
|
+
return false;
|
|
43
|
+
default:
|
|
44
|
+
return widget satisfies never;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Whether `value` is an answer at all, rather than a blank. */
|
|
49
|
+
const given = (value: unknown): boolean =>
|
|
50
|
+
value !== undefined && value !== "" && !(Array.isArray(value) && value.length === 0);
|
|
51
|
+
|
|
52
|
+
/** The question's outcome on submit: drawn and answered, left blank, or not drawable here. */
|
|
53
|
+
function outcomeOf(canDraw: boolean, value: unknown): QuestionOutcomeBody {
|
|
54
|
+
if (!canDraw) {
|
|
55
|
+
return { type: "unsupported" };
|
|
56
|
+
}
|
|
57
|
+
return given(value) ? { type: "answered", data: { value } } : { type: "skipped" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface FieldProps {
|
|
61
|
+
readonly question: QuestionBody;
|
|
62
|
+
readonly widget: ReadWidget;
|
|
63
|
+
readonly widgets: WidgetRenderers;
|
|
64
|
+
readonly value: unknown;
|
|
65
|
+
readonly onChange: (value: unknown) => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A select: radios for one pick, checkboxes for several, and a text box for the person's own answer. */
|
|
69
|
+
function SelectField({
|
|
70
|
+
question,
|
|
71
|
+
widget,
|
|
72
|
+
value,
|
|
73
|
+
onChange,
|
|
74
|
+
}: FieldProps & { readonly widget: Extract<ReadWidget, { type: "select" }> }): ReactNode {
|
|
75
|
+
const picked = Array.isArray(value) ? (value as string[]) : [];
|
|
76
|
+
const labels = widget.data.options.map((option) => option.label);
|
|
77
|
+
const other = picked.find((label) => !labels.includes(label)) ?? "";
|
|
78
|
+
const chosen = picked.filter((label) => labels.includes(label));
|
|
79
|
+
const set = (next: readonly string[], otherText: string) =>
|
|
80
|
+
onChange(otherText === "" ? [...next] : [...next, otherText]);
|
|
81
|
+
return (
|
|
82
|
+
<div className="subako-input-options" role={widget.data.multiple ? "group" : "radiogroup"}>
|
|
83
|
+
{widget.data.options.map((option) => (
|
|
84
|
+
<label key={option.label} className="subako-input-option">
|
|
85
|
+
<input
|
|
86
|
+
type={widget.data.multiple ? "checkbox" : "radio"}
|
|
87
|
+
name={question.id}
|
|
88
|
+
checked={chosen.includes(option.label)}
|
|
89
|
+
onChange={(event) => {
|
|
90
|
+
if (widget.data.multiple) {
|
|
91
|
+
set(
|
|
92
|
+
event.target.checked ? [...chosen, option.label] : chosen.filter((label) => label !== option.label),
|
|
93
|
+
other,
|
|
94
|
+
);
|
|
95
|
+
} else {
|
|
96
|
+
set([option.label], "");
|
|
97
|
+
}
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
<span>{option.label}</span>
|
|
101
|
+
{option.description === undefined ? null : <span className="subako-input-hint">{option.description}</span>}
|
|
102
|
+
</label>
|
|
103
|
+
))}
|
|
104
|
+
{widget.data.allow_other ? (
|
|
105
|
+
<input
|
|
106
|
+
className="subako-input-text"
|
|
107
|
+
aria-label="Other"
|
|
108
|
+
placeholder="Other"
|
|
109
|
+
value={other}
|
|
110
|
+
onChange={(event) => set(widget.data.multiple ? chosen : [], event.target.value)}
|
|
111
|
+
/>
|
|
112
|
+
) : null}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function Field(props: FieldProps): ReactNode {
|
|
118
|
+
const { question, widget, widgets, value, onChange } = props;
|
|
119
|
+
switch (widget.type) {
|
|
120
|
+
case "select":
|
|
121
|
+
return <SelectField {...props} widget={widget} />;
|
|
122
|
+
case "text":
|
|
123
|
+
return widget.data.multiline ? (
|
|
124
|
+
<textarea
|
|
125
|
+
className="subako-input-text"
|
|
126
|
+
aria-label={question.prompt}
|
|
127
|
+
rows={3}
|
|
128
|
+
value={typeof value === "string" ? value : ""}
|
|
129
|
+
onChange={(event) => onChange(event.target.value)}
|
|
130
|
+
/>
|
|
131
|
+
) : (
|
|
132
|
+
<input
|
|
133
|
+
className="subako-input-text"
|
|
134
|
+
aria-label={question.prompt}
|
|
135
|
+
value={typeof value === "string" ? value : ""}
|
|
136
|
+
onChange={(event) => onChange(event.target.value)}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
case "confirm":
|
|
140
|
+
return (
|
|
141
|
+
<div className="subako-input-options" role="radiogroup">
|
|
142
|
+
{[true, false].map((answer) => (
|
|
143
|
+
<label key={String(answer)} className="subako-input-option">
|
|
144
|
+
<input type="radio" name={question.id} checked={value === answer} onChange={() => onChange(answer)} />
|
|
145
|
+
<span>{answer ? "Yes" : "No"}</span>
|
|
146
|
+
</label>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
case "custom": {
|
|
151
|
+
const Render = widgets[widget.data.id];
|
|
152
|
+
return Render === undefined ? (
|
|
153
|
+
<Unsupported widget={widget} />
|
|
154
|
+
) : (
|
|
155
|
+
<Render props={widget.data.props} value={value} onChange={onChange} question={question} />
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
case "unsupported":
|
|
159
|
+
return <Unsupported widget={widget} />;
|
|
160
|
+
default:
|
|
161
|
+
return widget satisfies never;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function Unsupported({ widget }: { readonly widget: ReadWidget }): ReactNode {
|
|
166
|
+
return <div className="subako-input-hint">This app can’t show “{widgetName(widget)}”.</div>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* One form an agent put through `builtin:ask_user`: each question drawn with
|
|
171
|
+
* its widget, a Submit that sends one outcome per question, and a Dismiss.
|
|
172
|
+
* A custom widget draws with the renderer `widgets` registers under its id; a
|
|
173
|
+
* question this client cannot draw is sent as `unsupported`, so the agent can
|
|
174
|
+
* ask it another way. A form none of whose questions can be drawn shows only
|
|
175
|
+
* a button that tells the agent so.
|
|
176
|
+
*/
|
|
177
|
+
export function SubakoInputForm({ input, widgets = {}, onAnswer }: SubakoInputFormProps): ReactNode {
|
|
178
|
+
const [values, setValues] = useState<Readonly<Record<string, unknown>>>({});
|
|
179
|
+
const [sending, setSending] = useState(false);
|
|
180
|
+
const [error, setError] = useState<string | undefined>(undefined);
|
|
181
|
+
const questions = input.request.questions.map((question) => {
|
|
182
|
+
const widget = readWidget(question.widget);
|
|
183
|
+
return { question, widget, canDraw: drawable(widget, widgets) };
|
|
184
|
+
});
|
|
185
|
+
const send = (answer: InputAnswer) => {
|
|
186
|
+
setSending(true);
|
|
187
|
+
setError(undefined);
|
|
188
|
+
onAnswer(answer).then(
|
|
189
|
+
() => setSending(false),
|
|
190
|
+
(cause: unknown) => {
|
|
191
|
+
setSending(false);
|
|
192
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
const failure = error === undefined ? null : <div className="subako-input-error">{error}</div>;
|
|
197
|
+
|
|
198
|
+
if (questions.every(({ canDraw }) => !canDraw)) {
|
|
199
|
+
return (
|
|
200
|
+
<div className="subako-input-form" data-testid={`input-${input.call_id}`}>
|
|
201
|
+
<div className="subako-input-hint">This app can’t show the agent’s question.</div>
|
|
202
|
+
<div className="subako-input-actions">
|
|
203
|
+
<button type="button" disabled={sending} onClick={() => send(unsupportedAnswer(input))}>
|
|
204
|
+
Tell the agent
|
|
205
|
+
</button>
|
|
206
|
+
</div>
|
|
207
|
+
{failure}
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const missing = questions.some(
|
|
213
|
+
({ question, canDraw }) => question.required && canDraw && !given(values[question.id]),
|
|
214
|
+
);
|
|
215
|
+
return (
|
|
216
|
+
<form
|
|
217
|
+
className="subako-input-form"
|
|
218
|
+
data-testid={`input-${input.call_id}`}
|
|
219
|
+
onSubmit={(event) => {
|
|
220
|
+
event.preventDefault();
|
|
221
|
+
send(
|
|
222
|
+
submittedAnswer(
|
|
223
|
+
input,
|
|
224
|
+
Object.fromEntries(
|
|
225
|
+
questions.map(({ question, canDraw }) => [question.id, outcomeOf(canDraw, values[question.id])]),
|
|
226
|
+
),
|
|
227
|
+
),
|
|
228
|
+
);
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
{questions.map(({ question, widget }) => (
|
|
232
|
+
<fieldset key={question.id} className="subako-input-question">
|
|
233
|
+
<legend className="subako-input-prompt">
|
|
234
|
+
{question.header === undefined ? null : <strong>{question.header}: </strong>}
|
|
235
|
+
{question.prompt}
|
|
236
|
+
{question.required ? " *" : null}
|
|
237
|
+
</legend>
|
|
238
|
+
<Field
|
|
239
|
+
question={question}
|
|
240
|
+
widget={widget}
|
|
241
|
+
widgets={widgets}
|
|
242
|
+
value={values[question.id]}
|
|
243
|
+
onChange={(value) => setValues((previous) => ({ ...previous, [question.id]: value }))}
|
|
244
|
+
/>
|
|
245
|
+
</fieldset>
|
|
246
|
+
))}
|
|
247
|
+
<div className="subako-input-actions">
|
|
248
|
+
<button type="submit" disabled={sending || missing}>
|
|
249
|
+
Submit
|
|
250
|
+
</button>
|
|
251
|
+
<button type="button" disabled={sending} onClick={() => send({ type: "declined", data: {} })}>
|
|
252
|
+
Dismiss
|
|
253
|
+
</button>
|
|
254
|
+
</div>
|
|
255
|
+
{failure}
|
|
256
|
+
</form>
|
|
257
|
+
);
|
|
258
|
+
}
|
package/src/runtime.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { AppendMessage, AssistantRuntime } from "@assistant-ui/react";
|
|
4
4
|
import { useExternalStoreRuntime } from "@assistant-ui/react";
|
|
5
5
|
import { useSessionState } from "@subako-ai/react";
|
|
6
|
-
import type { LiveMessage, SessionState, TranscriptMessage } from "@subako-ai/sdk";
|
|
6
|
+
import type { InputAnswer, LiveMessage, SessionState, TranscriptMessage } from "@subako-ai/sdk";
|
|
7
7
|
import { useMemo } from "react";
|
|
8
8
|
import { convertLiveMessage, convertMessage } from "./convert.js";
|
|
9
9
|
|
|
@@ -22,6 +22,8 @@ export interface SubakoSession {
|
|
|
22
22
|
/** Cancels the active run. */
|
|
23
23
|
cancel(): Promise<void>;
|
|
24
24
|
resolveApproval(callId: string, decision: "allow" | "deny", message?: string): Promise<void>;
|
|
25
|
+
/** Answers a form the agent put through `builtin:ask_user`. */
|
|
26
|
+
answerInput(callId: string, answer: InputAnswer): Promise<void>;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** One message of the thread: one the log holds, or one a model call is still producing. */
|