@parlor/client 0.0.0-stage → 0.2.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 +22 -2
- package/dist/action-client.d.ts +42 -0
- package/dist/action-client.d.ts.map +1 -0
- package/dist/action-client.js +92 -0
- package/dist/action-client.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-client.d.ts +11 -0
- package/dist/plugin-client.d.ts.map +1 -0
- package/dist/plugin-client.js +14 -0
- package/dist/plugin-client.js.map +1 -0
- package/dist/query-keys.d.ts +3 -0
- package/dist/query-keys.d.ts.map +1 -0
- package/dist/query-keys.js +5 -0
- package/dist/query-keys.js.map +1 -0
- package/dist/react/action-query.d.ts +26 -0
- package/dist/react/action-query.d.ts.map +1 -0
- package/dist/react/action-query.js +33 -0
- package/dist/react/action-query.js.map +1 -0
- package/dist/react/agent-ui/activity-state.d.ts +20 -0
- package/dist/react/agent-ui/activity-state.d.ts.map +1 -0
- package/dist/react/agent-ui/activity-state.js +14 -0
- package/dist/react/agent-ui/activity-state.js.map +1 -0
- package/dist/react/agent-ui/chat-activity.d.ts +13 -0
- package/dist/react/agent-ui/chat-activity.d.ts.map +1 -0
- package/dist/react/agent-ui/chat-activity.js +26 -0
- package/dist/react/agent-ui/chat-activity.js.map +1 -0
- package/dist/react/agent-ui/chat-confirm.d.ts +28 -0
- package/dist/react/agent-ui/chat-confirm.d.ts.map +1 -0
- package/dist/react/agent-ui/chat-confirm.js +140 -0
- package/dist/react/agent-ui/chat-confirm.js.map +1 -0
- package/dist/react/agent-ui/chat.d.ts +7 -0
- package/dist/react/agent-ui/chat.d.ts.map +1 -0
- package/dist/react/agent-ui/chat.js +7 -0
- package/dist/react/agent-ui/chat.js.map +1 -0
- package/dist/react/agent-ui/dev.d.ts +12 -0
- package/dist/react/agent-ui/dev.d.ts.map +1 -0
- package/dist/react/agent-ui/dev.js +80 -0
- package/dist/react/agent-ui/dev.js.map +1 -0
- package/dist/react/agent-ui/runtime.d.ts +12 -0
- package/dist/react/agent-ui/runtime.d.ts.map +1 -0
- package/dist/react/agent-ui/runtime.js +50 -0
- package/dist/react/agent-ui/runtime.js.map +1 -0
- package/dist/react/agent-ui/use-agent-activity.d.ts +3 -0
- package/dist/react/agent-ui/use-agent-activity.d.ts.map +1 -0
- package/dist/react/agent-ui/use-agent-activity.js +7 -0
- package/dist/react/agent-ui/use-agent-activity.js.map +1 -0
- package/dist/react/agent-ui/use-agent-confirmations.d.ts +8 -0
- package/dist/react/agent-ui/use-agent-confirmations.d.ts.map +1 -0
- package/dist/react/agent-ui/use-agent-confirmations.js +12 -0
- package/dist/react/agent-ui/use-agent-confirmations.js.map +1 -0
- package/dist/react/agent-ui/use-agent-view.d.ts +11 -0
- package/dist/react/agent-ui/use-agent-view.d.ts.map +1 -0
- package/dist/react/agent-ui/use-agent-view.js +25 -0
- package/dist/react/agent-ui/use-agent-view.js.map +1 -0
- package/dist/react/chat/ask-fields.d.ts +13 -0
- package/dist/react/chat/ask-fields.d.ts.map +1 -0
- package/dist/react/chat/ask-fields.js +46 -0
- package/dist/react/chat/ask-fields.js.map +1 -0
- package/dist/react/chat/chat-ask.d.ts +32 -0
- package/dist/react/chat/chat-ask.d.ts.map +1 -0
- package/dist/react/chat/chat-ask.js +69 -0
- package/dist/react/chat/chat-ask.js.map +1 -0
- package/dist/react/chat/chat-composer.d.ts +18 -0
- package/dist/react/chat/chat-composer.d.ts.map +1 -0
- package/dist/react/chat/chat-composer.js +81 -0
- package/dist/react/chat/chat-composer.js.map +1 -0
- package/dist/react/chat/chat-root.d.ts +14 -0
- package/dist/react/chat/chat-root.d.ts.map +1 -0
- package/dist/react/chat/chat-root.js +21 -0
- package/dist/react/chat/chat-root.js.map +1 -0
- package/dist/react/chat/chat-thread.d.ts +18 -0
- package/dist/react/chat/chat-thread.d.ts.map +1 -0
- package/dist/react/chat/chat-thread.js +69 -0
- package/dist/react/chat/chat-thread.js.map +1 -0
- package/dist/react/chat/context.d.ts +6 -0
- package/dist/react/chat/context.d.ts.map +1 -0
- package/dist/react/chat/context.js +24 -0
- package/dist/react/chat/context.js.map +1 -0
- package/dist/react/chat/create.d.ts +15 -0
- package/dist/react/chat/create.d.ts.map +1 -0
- package/dist/react/chat/create.js +23 -0
- package/dist/react/chat/create.js.map +1 -0
- package/dist/react/chat/open-turns.d.ts +10 -0
- package/dist/react/chat/open-turns.d.ts.map +1 -0
- package/dist/react/chat/open-turns.js +60 -0
- package/dist/react/chat/open-turns.js.map +1 -0
- package/dist/react/chat/runtime.d.ts +167 -0
- package/dist/react/chat/runtime.d.ts.map +1 -0
- package/dist/react/chat/runtime.js +939 -0
- package/dist/react/chat/runtime.js.map +1 -0
- package/dist/react/chat/use-chat.d.ts +4 -0
- package/dist/react/chat/use-chat.d.ts.map +1 -0
- package/dist/react/chat/use-chat.js +22 -0
- package/dist/react/chat/use-chat.js.map +1 -0
- package/dist/react/chat/use-device-setup.d.ts +9 -0
- package/dist/react/chat/use-device-setup.d.ts.map +1 -0
- package/dist/react/chat/use-device-setup.js +17 -0
- package/dist/react/chat/use-device-setup.js.map +1 -0
- package/dist/react/hooks.d.ts +17 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +36 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/index.d.ts +21 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +14 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/provider.d.ts +41 -0
- package/dist/react/provider.d.ts.map +1 -0
- package/dist/react/provider.js +176 -0
- package/dist/react/provider.js.map +1 -0
- package/dist/react/retry.d.ts +5 -0
- package/dist/react/retry.d.ts.map +1 -0
- package/dist/react/retry.js +22 -0
- package/dist/react/retry.js.map +1 -0
- package/dist/react/sync-bridge.d.ts +28 -0
- package/dist/react/sync-bridge.d.ts.map +1 -0
- package/dist/react/sync-bridge.js +65 -0
- package/dist/react/sync-bridge.js.map +1 -0
- package/dist/react/sync.d.ts +51 -0
- package/dist/react/sync.d.ts.map +1 -0
- package/dist/react/sync.js +154 -0
- package/dist/react/sync.js.map +1 -0
- package/dist/react/use-capability.d.ts +6 -0
- package/dist/react/use-capability.d.ts.map +1 -0
- package/dist/react/use-capability.js +21 -0
- package/dist/react/use-capability.js.map +1 -0
- package/dist/react/use-devices.d.ts +34 -0
- package/dist/react/use-devices.d.ts.map +1 -0
- package/dist/react/use-devices.js +93 -0
- package/dist/react/use-devices.js.map +1 -0
- package/dist/react/use-link-device.d.ts +42 -0
- package/dist/react/use-link-device.d.ts.map +1 -0
- package/dist/react/use-link-device.js +58 -0
- package/dist/react/use-link-device.js.map +1 -0
- package/dist/register.d.ts +7 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +2 -0
- package/dist/register.js.map +1 -0
- package/dist/router/errors.d.ts +13 -0
- package/dist/router/errors.d.ts.map +1 -0
- package/dist/router/errors.js +16 -0
- package/dist/router/errors.js.map +1 -0
- package/dist/router/index.d.ts +4 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +4 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/mount.d.ts +22 -0
- package/dist/router/mount.d.ts.map +1 -0
- package/dist/router/mount.js +27 -0
- package/dist/router/mount.js.map +1 -0
- package/dist/router/router.d.ts +29 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/router.js +44 -0
- package/dist/router/router.js.map +1 -0
- package/dist/session.d.ts +121 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +5 -0
- package/dist/session.js.map +1 -0
- package/dist/ssr/index.d.ts +2 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +2 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/ssr/render.d.ts +18 -0
- package/dist/ssr/render.d.ts.map +1 -0
- package/dist/ssr/render.js +96 -0
- package/dist/ssr/render.js.map +1 -0
- package/package.json +70 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, use, useMemo, } from "react";
|
|
3
|
+
import { askFields, askValues } from "./ask-fields.js";
|
|
4
|
+
import { useChatRuntime } from "./context.js";
|
|
5
|
+
const AskScope = createContext(undefined);
|
|
6
|
+
const report = (error) => console.error("[parlor] The chat could not answer:", error);
|
|
7
|
+
/**
|
|
8
|
+
* The agent's own permission prompt or question: `role="group"` named by its question.
|
|
9
|
+
* `data-approval` is `presence` (answering triggers the OS prompt) or `computer` (answered
|
|
10
|
+
* on the computer; choices disabled). `data-state` is `open`, `answered` or `closed`.
|
|
11
|
+
* Escape declines.
|
|
12
|
+
*/
|
|
13
|
+
export function Ask({ item, onKeyDown, ...props }) {
|
|
14
|
+
const runtime = useChatRuntime("Chat.Ask");
|
|
15
|
+
const decline = (event) => {
|
|
16
|
+
onKeyDown?.(event);
|
|
17
|
+
if (event.defaultPrevented || event.key !== "Escape")
|
|
18
|
+
return;
|
|
19
|
+
const rejection = item.choices.find((choice) => choice.effect.startsWith("reject"));
|
|
20
|
+
if (!rejection || item.answer.state !== "open" || item.approval === "computer")
|
|
21
|
+
return;
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
event.stopPropagation();
|
|
24
|
+
runtime.answer(item.id, rejection.id).catch(report);
|
|
25
|
+
};
|
|
26
|
+
return (_jsx(AskScope, { value: item, children: _jsx("div", { role: "group", "aria-label": item.question, tabIndex: -1, ...props, "data-approval": item.approval, "data-state": item.answer.state, onKeyDown: decline }) }));
|
|
27
|
+
}
|
|
28
|
+
/** `type="button"`; answers the enclosing ask with this choice. */
|
|
29
|
+
export function AskChoice({ choice, onClick, ...props }) {
|
|
30
|
+
const runtime = useChatRuntime("Chat.AskChoice");
|
|
31
|
+
const ask = use(AskScope);
|
|
32
|
+
if (!ask)
|
|
33
|
+
throw new Error("Chat.AskChoice must be rendered inside Chat.Ask.");
|
|
34
|
+
return (_jsx("button", { type: "button", disabled: ask.approval === "computer" || ask.answer.state !== "open", "data-effect": choice.effect, "aria-pressed": ask.answer.state === "answered" ? ask.answer.choice === choice.id : undefined, ...props, onClick: (event) => {
|
|
35
|
+
onClick?.(event);
|
|
36
|
+
if (!event.defaultPrevented)
|
|
37
|
+
runtime.answer(ask.id, choice.id).catch(report);
|
|
38
|
+
} }));
|
|
39
|
+
}
|
|
40
|
+
const isAllow = (choice) => choice.effect.startsWith("allow");
|
|
41
|
+
/**
|
|
42
|
+
* A form ask's `<form>`: one `<input name="{property}">` per property of its schema (`number`
|
|
43
|
+
* for numbers and integers, `checkbox` for booleans, text otherwise). Submitting answers the
|
|
44
|
+
* ask with its allow choice and the values typed per the schema. Its children, a submit
|
|
45
|
+
* button for one, go after the inputs. Everything is disabled once the ask is not open or
|
|
46
|
+
* is answered on the computer. Renders nothing for an ask without a form.
|
|
47
|
+
*/
|
|
48
|
+
export function AskForm({ onSubmit, children, ...props }) {
|
|
49
|
+
const runtime = useChatRuntime("Chat.AskForm");
|
|
50
|
+
const ask = use(AskScope);
|
|
51
|
+
if (!ask)
|
|
52
|
+
throw new Error("Chat.AskForm must be rendered inside Chat.Ask.");
|
|
53
|
+
const { form } = ask;
|
|
54
|
+
const fields = useMemo(() => (form ? askFields(form) : []), [form]);
|
|
55
|
+
const accept = ask.choices.find(isAllow);
|
|
56
|
+
if (!form || !accept)
|
|
57
|
+
return null;
|
|
58
|
+
return (_jsx("form", { ...props, onSubmit: (event) => {
|
|
59
|
+
onSubmit?.(event);
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
runtime
|
|
62
|
+
.answer(ask.id, accept.id, askValues(fields, event.currentTarget))
|
|
63
|
+
.catch(report);
|
|
64
|
+
}, children: _jsxs("fieldset", { disabled: ask.approval === "computer" || ask.answer.state !== "open", children: [fields.map((field) => (_jsx(AskInput, { field: field }, field.name))), children] }) }));
|
|
65
|
+
}
|
|
66
|
+
function AskInput({ field }) {
|
|
67
|
+
return (_jsxs("label", { "data-type": field.type, children: [_jsx("span", { children: field.label }), _jsx("input", { name: field.name, type: field.type, required: field.required && field.type !== "checkbox", step: field.type === "number" ? (field.integer ? 1 : "any") : undefined })] }));
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=chat-ask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-ask.js","sourceRoot":"","sources":["../../../src/react/chat/chat-ask.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { AgentConfirmation } from \"@parlor/agent-ui/client\";\nimport type { ChatItem } from \"@parlor/chat\";\nimport {\n type ComponentProps,\n createContext,\n type KeyboardEvent,\n type ReactNode,\n use,\n useMemo,\n} from \"react\";\n\nimport { type AskField, askFields, askValues } from \"./ask-fields.js\";\nimport { useChatRuntime } from \"./context.js\";\n\ntype AskItem = Extract<ChatItem<AgentConfirmation>, { kind: \"ask\" }>;\n\nconst AskScope = createContext<AskItem | undefined>(undefined);\n\nconst report = (error: unknown) =>\n console.error(\"[parlor] The chat could not answer:\", error);\n\nexport interface ChatAskProps extends ComponentProps<\"div\"> {\n readonly item: AskItem;\n}\n\n/**\n * The agent's own permission prompt or question: `role=\"group\"` named by its question.\n * `data-approval` is `presence` (answering triggers the OS prompt) or `computer` (answered\n * on the computer; choices disabled). `data-state` is `open`, `answered` or `closed`.\n * Escape declines.\n */\nexport function Ask({ item, onKeyDown, ...props }: ChatAskProps): ReactNode {\n const runtime = useChatRuntime(\"Chat.Ask\");\n const decline = (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n if (event.defaultPrevented || event.key !== \"Escape\") return;\n const rejection = item.choices.find((choice) => choice.effect.startsWith(\"reject\"));\n if (!rejection || item.answer.state !== \"open\" || item.approval === \"computer\")\n return;\n event.preventDefault();\n event.stopPropagation();\n runtime.answer(item.id, rejection.id).catch(report);\n };\n return (\n <AskScope value={item}>\n <div\n role=\"group\"\n aria-label={item.question}\n tabIndex={-1}\n {...props}\n data-approval={item.approval}\n data-state={item.answer.state}\n onKeyDown={decline}\n />\n </AskScope>\n );\n}\n\nexport interface ChatAskChoiceProps extends ComponentProps<\"button\"> {\n readonly choice: AskItem[\"choices\"][number];\n}\n\n/** `type=\"button\"`; answers the enclosing ask with this choice. */\nexport function AskChoice({ choice, onClick, ...props }: ChatAskChoiceProps): ReactNode {\n const runtime = useChatRuntime(\"Chat.AskChoice\");\n const ask = use(AskScope);\n if (!ask) throw new Error(\"Chat.AskChoice must be rendered inside Chat.Ask.\");\n return (\n <button\n type=\"button\"\n disabled={ask.approval === \"computer\" || ask.answer.state !== \"open\"}\n data-effect={choice.effect}\n aria-pressed={\n ask.answer.state === \"answered\" ? ask.answer.choice === choice.id : undefined\n }\n {...props}\n onClick={(event) => {\n onClick?.(event);\n if (!event.defaultPrevented) runtime.answer(ask.id, choice.id).catch(report);\n }}\n />\n );\n}\n\nconst isAllow = (choice: AskItem[\"choices\"][number]) => choice.effect.startsWith(\"allow\");\n\nexport type ChatAskFormProps = ComponentProps<\"form\">;\n\n/**\n * A form ask's `<form>`: one `<input name=\"{property}\">` per property of its schema (`number`\n * for numbers and integers, `checkbox` for booleans, text otherwise). Submitting answers the\n * ask with its allow choice and the values typed per the schema. Its children, a submit\n * button for one, go after the inputs. Everything is disabled once the ask is not open or\n * is answered on the computer. Renders nothing for an ask without a form.\n */\nexport function AskForm({ onSubmit, children, ...props }: ChatAskFormProps): ReactNode {\n const runtime = useChatRuntime(\"Chat.AskForm\");\n const ask = use(AskScope);\n if (!ask) throw new Error(\"Chat.AskForm must be rendered inside Chat.Ask.\");\n const { form } = ask;\n const fields = useMemo(() => (form ? askFields(form) : []), [form]);\n const accept = ask.choices.find(isAllow);\n if (!form || !accept) return null;\n return (\n <form\n {...props}\n onSubmit={(event) => {\n onSubmit?.(event);\n event.preventDefault();\n runtime\n .answer(ask.id, accept.id, askValues(fields, event.currentTarget))\n .catch(report);\n }}\n >\n <fieldset disabled={ask.approval === \"computer\" || ask.answer.state !== \"open\"}>\n {fields.map((field) => (\n <AskInput key={field.name} field={field} />\n ))}\n {children}\n </fieldset>\n </form>\n );\n}\n\nfunction AskInput({ field }: { readonly field: AskField }): ReactNode {\n return (\n <label data-type={field.type}>\n <span>{field.label}</span>\n <input\n name={field.name}\n type={field.type}\n required={field.required && field.type !== \"checkbox\"}\n step={field.type === \"number\" ? (field.integer ? 1 : \"any\") : undefined}\n />\n </label>\n );\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
/** A `<form>` whose submit sends the draft; `data-running` while a turn runs. */
|
|
3
|
+
export declare function Composer({ onSubmit, ...props }: ComponentProps<"form">): ReactNode;
|
|
4
|
+
/**
|
|
5
|
+
* A `<textarea>` bound to the draft: Enter sends, Shift+Enter breaks the line, IME
|
|
6
|
+
* composition never sends. Disabled while no agent can be chosen.
|
|
7
|
+
*/
|
|
8
|
+
export declare function Input({ onChange, onKeyDown, ...props }: Omit<ComponentProps<"textarea">, "value" | "defaultValue">): ReactNode;
|
|
9
|
+
/** `type="submit"`; disabled while the draft is empty or no agent is chosen. */
|
|
10
|
+
export declare function Send({ children, ...props }: ComponentProps<"button">): ReactNode;
|
|
11
|
+
/** `type="button"`; rendered only while a turn runs. Stops it, keeping the partial text. */
|
|
12
|
+
export declare function Stop({ children, onClick, ...props }: ComponentProps<"button">): ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* The chat's state in words, for a live region: a `p[role=status]` that is empty when
|
|
15
|
+
* there is nothing to say, so a wait never shows without text. `children` replaces the text.
|
|
16
|
+
*/
|
|
17
|
+
export declare function Status({ children, ...props }: ComponentProps<"p">): ReactNode;
|
|
18
|
+
//# sourceMappingURL=chat-composer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-composer.d.ts","sourceRoot":"","sources":["../../../src/react/chat/chat-composer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAC;AAQtE,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG,SAAS,CAiBlF;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC,GAAG,SAAS,CAuBxE;AAED,gFAAgF;AAChF,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,SAAS,CAOhF;AAED,4FAA4F;AAC5F,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,OAAO,EACP,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,SAAS,CAgBtC;AAgBD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,GAAG,CAAC,GAAG,SAAS,CAa7E"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { canSend, useChatRuntime, useChatState } from "./context.js";
|
|
3
|
+
const report = (error) => console.error("[parlor] The chat could not do that:", error);
|
|
4
|
+
/** A `<form>` whose submit sends the draft; `data-running` while a turn runs. */
|
|
5
|
+
export function Composer({ onSubmit, ...props }) {
|
|
6
|
+
const runtime = useChatRuntime("Chat.Composer");
|
|
7
|
+
const state = useChatState("Chat.Composer");
|
|
8
|
+
const submit = (event) => {
|
|
9
|
+
onSubmit?.(event);
|
|
10
|
+
event.preventDefault();
|
|
11
|
+
if (canSend(state))
|
|
12
|
+
runtime.send().catch(report);
|
|
13
|
+
};
|
|
14
|
+
return (_jsx("form", { ...props, onSubmit: submit, "data-running": state.status === "running" ? "" : undefined }));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A `<textarea>` bound to the draft: Enter sends, Shift+Enter breaks the line, IME
|
|
18
|
+
* composition never sends. Disabled while no agent can be chosen.
|
|
19
|
+
*/
|
|
20
|
+
export function Input({ onChange, onKeyDown, ...props }) {
|
|
21
|
+
const runtime = useChatRuntime("Chat.Input");
|
|
22
|
+
const state = useChatState("Chat.Input");
|
|
23
|
+
const keyDown = (event) => {
|
|
24
|
+
onKeyDown?.(event);
|
|
25
|
+
if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey)
|
|
26
|
+
return;
|
|
27
|
+
if (event.nativeEvent.isComposing)
|
|
28
|
+
return;
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
if (canSend(state))
|
|
31
|
+
runtime.send().catch(report);
|
|
32
|
+
};
|
|
33
|
+
return (_jsx("textarea", { "aria-label": "Message", disabled: state.agent === undefined || state.status === "unavailable", ...props, value: state.draft, onChange: (event) => {
|
|
34
|
+
onChange?.(event);
|
|
35
|
+
runtime.setDraft(event.target.value);
|
|
36
|
+
}, onKeyDown: keyDown }));
|
|
37
|
+
}
|
|
38
|
+
/** `type="submit"`; disabled while the draft is empty or no agent is chosen. */
|
|
39
|
+
export function Send({ children, ...props }) {
|
|
40
|
+
const state = useChatState("Chat.Send");
|
|
41
|
+
return (_jsx("button", { type: "submit", disabled: !canSend(state), ...props, children: children ?? "Send" }));
|
|
42
|
+
}
|
|
43
|
+
/** `type="button"`; rendered only while a turn runs. Stops it, keeping the partial text. */
|
|
44
|
+
export function Stop({ children, onClick, ...props }) {
|
|
45
|
+
const runtime = useChatRuntime("Chat.Stop");
|
|
46
|
+
const state = useChatState("Chat.Stop");
|
|
47
|
+
if (state.status !== "running" && state.status !== "waiting")
|
|
48
|
+
return null;
|
|
49
|
+
return (_jsx("button", { type: "button", ...props, onClick: (event) => {
|
|
50
|
+
onClick?.(event);
|
|
51
|
+
if (!event.defaultPrevented)
|
|
52
|
+
runtime.stop().catch(report);
|
|
53
|
+
}, children: children ?? "Stop" }));
|
|
54
|
+
}
|
|
55
|
+
function statusText({ status, wait, setup, link, presenceDue }) {
|
|
56
|
+
if (setup?.step === "passkey")
|
|
57
|
+
return "Waiting for the passkey";
|
|
58
|
+
if (link.status === "unlinked")
|
|
59
|
+
return "Link this device to send messages";
|
|
60
|
+
if (wait) {
|
|
61
|
+
return wait.reason === "offline"
|
|
62
|
+
? `Waiting for ${wait.hostName} to come online`
|
|
63
|
+
: `Waiting for ${wait.hostName} to approve this device`;
|
|
64
|
+
}
|
|
65
|
+
if (status === "sending")
|
|
66
|
+
return "Sending...";
|
|
67
|
+
if (status === "running")
|
|
68
|
+
return "Working...";
|
|
69
|
+
if (presenceDue)
|
|
70
|
+
return "Sending will ask for Face ID or Touch ID";
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The chat's state in words, for a live region: a `p[role=status]` that is empty when
|
|
75
|
+
* there is nothing to say, so a wait never shows without text. `children` replaces the text.
|
|
76
|
+
*/
|
|
77
|
+
export function Status({ children, ...props }) {
|
|
78
|
+
const state = useChatState("Chat.Status");
|
|
79
|
+
return (_jsx("p", { role: "status", ...props, "data-status": state.status, "data-wait": state.wait?.reason, "data-unavailable": state.unavailable?.reason, children: children ?? statusText(state) }));
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=chat-composer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-composer.js","sourceRoot":"","sources":["../../../src/react/chat/chat-composer.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentProps, KeyboardEvent, ReactNode } from \"react\";\n\nimport { canSend, useChatRuntime, useChatState } from \"./context.js\";\nimport type { ChatState } from \"./runtime.js\";\n\nconst report = (error: unknown) =>\n console.error(\"[parlor] The chat could not do that:\", error);\n\n/** A `<form>` whose submit sends the draft; `data-running` while a turn runs. */\nexport function Composer({ onSubmit, ...props }: ComponentProps<\"form\">): ReactNode {\n const runtime = useChatRuntime(\"Chat.Composer\");\n const state = useChatState(\"Chat.Composer\");\n const submit = (\n event: Parameters<NonNullable<ComponentProps<\"form\">[\"onSubmit\"]>>[0],\n ) => {\n onSubmit?.(event);\n event.preventDefault();\n if (canSend(state)) runtime.send().catch(report);\n };\n return (\n <form\n {...props}\n onSubmit={submit}\n data-running={state.status === \"running\" ? \"\" : undefined}\n />\n );\n}\n\n/**\n * A `<textarea>` bound to the draft: Enter sends, Shift+Enter breaks the line, IME\n * composition never sends. Disabled while no agent can be chosen.\n */\nexport function Input({\n onChange,\n onKeyDown,\n ...props\n}: Omit<ComponentProps<\"textarea\">, \"value\" | \"defaultValue\">): ReactNode {\n const runtime = useChatRuntime(\"Chat.Input\");\n const state = useChatState(\"Chat.Input\");\n const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n onKeyDown?.(event);\n if (event.defaultPrevented || event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing) return;\n event.preventDefault();\n if (canSend(state)) runtime.send().catch(report);\n };\n return (\n <textarea\n aria-label=\"Message\"\n disabled={state.agent === undefined || state.status === \"unavailable\"}\n {...props}\n value={state.draft}\n onChange={(event) => {\n onChange?.(event);\n runtime.setDraft(event.target.value);\n }}\n onKeyDown={keyDown}\n />\n );\n}\n\n/** `type=\"submit\"`; disabled while the draft is empty or no agent is chosen. */\nexport function Send({ children, ...props }: ComponentProps<\"button\">): ReactNode {\n const state = useChatState(\"Chat.Send\");\n return (\n <button type=\"submit\" disabled={!canSend(state)} {...props}>\n {children ?? \"Send\"}\n </button>\n );\n}\n\n/** `type=\"button\"`; rendered only while a turn runs. Stops it, keeping the partial text. */\nexport function Stop({\n children,\n onClick,\n ...props\n}: ComponentProps<\"button\">): ReactNode {\n const runtime = useChatRuntime(\"Chat.Stop\");\n const state = useChatState(\"Chat.Stop\");\n if (state.status !== \"running\" && state.status !== \"waiting\") return null;\n return (\n <button\n type=\"button\"\n {...props}\n onClick={(event) => {\n onClick?.(event);\n if (!event.defaultPrevented) runtime.stop().catch(report);\n }}\n >\n {children ?? \"Stop\"}\n </button>\n );\n}\n\nfunction statusText({ status, wait, setup, link, presenceDue }: ChatState): string {\n if (setup?.step === \"passkey\") return \"Waiting for the passkey\";\n if (link.status === \"unlinked\") return \"Link this device to send messages\";\n if (wait) {\n return wait.reason === \"offline\"\n ? `Waiting for ${wait.hostName} to come online`\n : `Waiting for ${wait.hostName} to approve this device`;\n }\n if (status === \"sending\") return \"Sending...\";\n if (status === \"running\") return \"Working...\";\n if (presenceDue) return \"Sending will ask for Face ID or Touch ID\";\n return \"\";\n}\n\n/**\n * The chat's state in words, for a live region: a `p[role=status]` that is empty when\n * there is nothing to say, so a wait never shows without text. `children` replaces the text.\n */\nexport function Status({ children, ...props }: ComponentProps<\"p\">): ReactNode {\n const state = useChatState(\"Chat.Status\");\n return (\n <p\n role=\"status\"\n {...props}\n data-status={state.status}\n data-wait={state.wait?.reason}\n data-unavailable={state.unavailable?.reason}\n >\n {children ?? statusText(state)}\n </p>\n );\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface ChatRootProps {
|
|
3
|
+
/** The thread to show. When omitted, the most recent one, or a blank one for a user with none. */
|
|
4
|
+
readonly threadId?: string | undefined;
|
|
5
|
+
readonly children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Owns one chat runtime for its subtree: the `chat` channel on the existing sync stream,
|
|
9
|
+
* its thread in presence `watching` so inline confirmations reach this client, and the
|
|
10
|
+
* confirmation subscription. Renders no DOM of its own. When the signed-in user changes,
|
|
11
|
+
* the subtree remounts with a fresh runtime, so nothing of the previous user remains.
|
|
12
|
+
*/
|
|
13
|
+
export declare function Root({ threadId, children }: ChatRootProps): ReactNode;
|
|
14
|
+
//# sourceMappingURL=chat-root.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-root.d.ts","sourceRoot":"","sources":["../../../src/react/chat/chat-root.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAC;AAM3D,MAAM,WAAW,aAAa;IAC5B,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,aAAa,GAAG,SAAS,CAMrE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo } from "react";
|
|
3
|
+
import { useFramework, useIdentityEpoch } from "../provider.js";
|
|
4
|
+
import { ChatScope } from "./context.js";
|
|
5
|
+
import { createRootRuntime } from "./create.js";
|
|
6
|
+
/**
|
|
7
|
+
* Owns one chat runtime for its subtree: the `chat` channel on the existing sync stream,
|
|
8
|
+
* its thread in presence `watching` so inline confirmations reach this client, and the
|
|
9
|
+
* confirmation subscription. Renders no DOM of its own. When the signed-in user changes,
|
|
10
|
+
* the subtree remounts with a fresh runtime, so nothing of the previous user remains.
|
|
11
|
+
*/
|
|
12
|
+
export function Root({ threadId, children }) {
|
|
13
|
+
return (_jsx(UserChat, { threadId: threadId, children: children }, useIdentityEpoch()));
|
|
14
|
+
}
|
|
15
|
+
function UserChat({ threadId, children }) {
|
|
16
|
+
const { client, agentUi, auth, sync } = useFramework();
|
|
17
|
+
const runtime = useMemo(() => createRootRuntime({ client, agentUi, auth, sync, threadId }), [client, agentUi, auth, sync, threadId]);
|
|
18
|
+
useEffect(() => runtime.start(), [runtime]);
|
|
19
|
+
return _jsx(ChatScope, { value: runtime, children: children });
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=chat-root.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-root.js","sourceRoot":"","sources":["../../../src/react/chat/chat-root.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAkB,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAQhD;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAiB;IACxD,OAAO,CACL,KAAC,QAAQ,IAA0B,QAAQ,EAAE,QAAQ,YAClD,QAAQ,IADI,gBAAgB,EAAE,CAEtB,CACZ,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAiB;IACrD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,YAAY,EAAE,CAAC;IACvD,MAAM,OAAO,GAAG,OAAO,CACrB,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAClE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CACxC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAE5C,OAAO,KAAC,SAAS,IAAC,KAAK,EAAE,OAAO,YAAG,QAAQ,GAAa,CAAC;AAC3D,CAAC","sourcesContent":["import { type ReactNode, useEffect, useMemo } from \"react\";\n\nimport { useFramework, useIdentityEpoch } from \"../provider.js\";\nimport { ChatScope } from \"./context.js\";\nimport { createRootRuntime } from \"./create.js\";\n\nexport interface ChatRootProps {\n /** The thread to show. When omitted, the most recent one, or a blank one for a user with none. */\n readonly threadId?: string | undefined;\n readonly children: ReactNode;\n}\n\n/**\n * Owns one chat runtime for its subtree: the `chat` channel on the existing sync stream,\n * its thread in presence `watching` so inline confirmations reach this client, and the\n * confirmation subscription. Renders no DOM of its own. When the signed-in user changes,\n * the subtree remounts with a fresh runtime, so nothing of the previous user remains.\n */\nexport function Root({ threadId, children }: ChatRootProps): ReactNode {\n return (\n <UserChat key={useIdentityEpoch()} threadId={threadId}>\n {children}\n </UserChat>\n );\n}\n\nfunction UserChat({ threadId, children }: ChatRootProps): ReactNode {\n const { client, agentUi, auth, sync } = useFramework();\n const runtime = useMemo(\n () => createRootRuntime({ client, agentUi, auth, sync, threadId }),\n [client, agentUi, auth, sync, threadId],\n );\n\n useEffect(() => runtime.start(), [runtime]);\n\n return <ChatScope value={runtime}>{children}</ChatScope>;\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentConfirmation } from "@parlor/agent-ui/client";
|
|
2
|
+
import type { ChatItem } from "@parlor/chat";
|
|
3
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
4
|
+
export interface ChatThreadProps extends Omit<ComponentProps<"ol">, "children"> {
|
|
5
|
+
readonly children: (item: ChatItem<AgentConfirmation>) => ReactNode;
|
|
6
|
+
readonly empty?: ReactNode;
|
|
7
|
+
/** The first row while the thread has earlier items, such as a `Chat.LoadOlder`. */
|
|
8
|
+
readonly older?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* `role="log"`, one `<li data-kind data-streaming>` per item keyed by item id, so a
|
|
12
|
+
* streamed reply keeps its row when its durable twin lands. Stays scrolled to the end
|
|
13
|
+
* while the user is there and never steals focus; `data-status` mirrors the chat status.
|
|
14
|
+
*/
|
|
15
|
+
export declare function Thread({ children, empty, older, ref, onScroll, ...props }: ChatThreadProps): import("react").JSX.Element;
|
|
16
|
+
/** `type="button"`; rendered only while the thread has earlier items, which it reads. */
|
|
17
|
+
export declare function LoadOlder({ children, onClick, ...props }: ComponentProps<"button">): import("react").JSX.Element | null;
|
|
18
|
+
//# sourceMappingURL=chat-thread.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-thread.d.ts","sourceRoot":"","sources":["../../../src/react/chat/chat-thread.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AAIf,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC7E,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,KAAK,SAAS,CAAC;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,oFAAoF;IACpF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;CAC5B;AAMD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,KAAK,EACL,KAAK,EACL,GAAG,EACH,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,eAAe,+BAmEjB;AAED,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,CAAC,QAAQ,CAAC,sCAqBlF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, useCallback, useLayoutEffect, useRef, } from "react";
|
|
3
|
+
import { useChatRuntime, useChatState } from "./context.js";
|
|
4
|
+
const NEAR_END_PX = 24;
|
|
5
|
+
const flag = (value) => (value ? "true" : "false");
|
|
6
|
+
/**
|
|
7
|
+
* `role="log"`, one `<li data-kind data-streaming>` per item keyed by item id, so a
|
|
8
|
+
* streamed reply keeps its row when its durable twin lands. Stays scrolled to the end
|
|
9
|
+
* while the user is there and never steals focus; `data-status` mirrors the chat status.
|
|
10
|
+
*/
|
|
11
|
+
export function Thread({ children, empty, older, ref, onScroll, ...props }) {
|
|
12
|
+
const { items, status, hasOlder } = useChatState("Chat.Thread");
|
|
13
|
+
const list = useRef(null);
|
|
14
|
+
const atEnd = useRef(true);
|
|
15
|
+
const anchor = useRef({
|
|
16
|
+
height: 0,
|
|
17
|
+
});
|
|
18
|
+
const attach = useCallback((element) => {
|
|
19
|
+
list.current = element;
|
|
20
|
+
if (typeof ref === "function")
|
|
21
|
+
return ref(element);
|
|
22
|
+
if (ref) {
|
|
23
|
+
ref.current = element;
|
|
24
|
+
return () => {
|
|
25
|
+
ref.current = null;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}, [ref]);
|
|
29
|
+
const track = (event) => {
|
|
30
|
+
onScroll?.(event);
|
|
31
|
+
const element = event.currentTarget;
|
|
32
|
+
atEnd.current =
|
|
33
|
+
element.scrollHeight - element.scrollTop - element.clientHeight <= NEAR_END_PX;
|
|
34
|
+
};
|
|
35
|
+
useLayoutEffect(() => {
|
|
36
|
+
const element = list.current;
|
|
37
|
+
if (!element)
|
|
38
|
+
return;
|
|
39
|
+
const first = items[0]?.id;
|
|
40
|
+
const before = anchor.current;
|
|
41
|
+
if (atEnd.current)
|
|
42
|
+
element.scrollTop = element.scrollHeight;
|
|
43
|
+
else if (before.first !== undefined &&
|
|
44
|
+
first !== before.first &&
|
|
45
|
+
items.some((item) => item.id === before.first)) {
|
|
46
|
+
element.scrollTop += element.scrollHeight - before.height;
|
|
47
|
+
}
|
|
48
|
+
anchor.current = { first, height: element.scrollHeight };
|
|
49
|
+
});
|
|
50
|
+
return (_jsxs(Fragment, { children: [_jsxs("ol", { role: "log", ...props, ref: attach, onScroll: track, "data-status": status, children: [hasOlder && older !== undefined ? _jsx("li", { "data-kind": "older", children: older }) : null, items.map((item) => (_jsx("li", { "data-item-id": item.id, "data-kind": item.kind, "data-streaming": "streaming" in item ? flag(item.streaming) : undefined, "data-interrupted": item.kind === "reply" || item.kind === "reasoning"
|
|
51
|
+
? flag(item.interrupted)
|
|
52
|
+
: undefined, children: children(item) }, item.id)))] }), items.length === 0 ? empty : null] }));
|
|
53
|
+
}
|
|
54
|
+
/** `type="button"`; rendered only while the thread has earlier items, which it reads. */
|
|
55
|
+
export function LoadOlder({ children, onClick, ...props }) {
|
|
56
|
+
const runtime = useChatRuntime("Chat.LoadOlder");
|
|
57
|
+
const { hasOlder, loadingOlder } = useChatState("Chat.LoadOlder");
|
|
58
|
+
if (!hasOlder)
|
|
59
|
+
return null;
|
|
60
|
+
return (_jsx("button", { type: "button", disabled: loadingOlder, ...props, onClick: (event) => {
|
|
61
|
+
onClick?.(event);
|
|
62
|
+
if (!event.defaultPrevented) {
|
|
63
|
+
runtime.loadOlder().catch((error) => {
|
|
64
|
+
console.error("[parlor] The chat could not do that:", error);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}, children: children ?? (loadingOlder ? "Loading…" : "Show earlier messages") }));
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=chat-thread.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-thread.js","sourceRoot":"","sources":["../../../src/react/chat/chat-thread.tsx"],"names":[],"mappings":";AAEA,OAAO,EAEL,QAAQ,EAGR,WAAW,EACX,eAAe,EACf,MAAM,GACP,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAS5D,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,EACrB,QAAQ,EACR,KAAK,EACL,KAAK,EACL,GAAG,EACH,QAAQ,EACR,GAAG,KAAK,EACQ;IAChB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,CACnB;QACE,MAAM,EAAE,CAAC;KACV,CACF,CAAC;IACF,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,OAAgC,EAAE,EAAE;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,OAAO,GAAG,KAAK,UAAU;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,GAAG,EAAE,CAAC;YACR,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC;YACtB,OAAO,GAAG,EAAE;gBACV,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,CAAC,CAAC;QACJ,CAAC;IACH,CAAC,EACD,CAAC,GAAG,CAAC,CACN,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,KAAgC,EAAE,EAAE;QACjD,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC;QACpC,KAAK,CAAC,OAAO;YACX,OAAO,CAAC,YAAY,GAAG,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,YAAY,IAAI,WAAW,CAAC;IACnF,CAAC,CAAC;IACF,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,IAAI,KAAK,CAAC,OAAO;YAAE,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC;aACvD,IACH,MAAM,CAAC,KAAK,KAAK,SAAS;YAC1B,KAAK,KAAK,MAAM,CAAC,KAAK;YACtB,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,KAAK,CAAC,EAC9C,CAAC;YACD,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5D,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,OAAO,CACL,MAAC,QAAQ,eACP,cAAI,IAAI,EAAC,KAAK,KAAK,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,iBAAe,MAAM,aACxE,QAAQ,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,0BAAc,OAAO,YAAE,KAAK,GAAM,CAAC,CAAC,CAAC,IAAI,EAC3E,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,6BAEgB,IAAI,CAAC,EAAE,eACV,IAAI,CAAC,IAAI,oBACJ,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,sBAEpE,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;4BAChD,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;4BACxB,CAAC,CAAC,SAAS,YAGd,QAAQ,CAAC,IAAI,CAAC,IAVV,IAAI,CAAC,EAAE,CAWT,CACN,CAAC,IACC,EACJ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IACzB,CACZ,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAA4B;IACjF,MAAM,OAAO,GAAG,cAAc,CAAC,gBAAgB,CAAC,CAAC;IACjD,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;IAClE,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,YAAY,KAClB,KAAK,EACT,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACjB,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YACjB,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC;gBAC5B,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;oBAC3C,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,KAAK,CAAC,CAAC;gBAC/D,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,YAEA,QAAQ,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,uBAAuB,CAAC,GAC3D,CACV,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentConfirmation } from \"@parlor/agent-ui/client\";\nimport type { ChatItem } from \"@parlor/chat\";\nimport {\n type ComponentProps,\n Fragment,\n type ReactNode,\n type UIEvent,\n useCallback,\n useLayoutEffect,\n useRef,\n} from \"react\";\n\nimport { useChatRuntime, useChatState } from \"./context.js\";\n\nexport interface ChatThreadProps extends Omit<ComponentProps<\"ol\">, \"children\"> {\n readonly children: (item: ChatItem<AgentConfirmation>) => ReactNode;\n readonly empty?: ReactNode;\n /** The first row while the thread has earlier items, such as a `Chat.LoadOlder`. */\n readonly older?: ReactNode;\n}\n\nconst NEAR_END_PX = 24;\n\nconst flag = (value: boolean) => (value ? \"true\" : \"false\");\n\n/**\n * `role=\"log\"`, one `<li data-kind data-streaming>` per item keyed by item id, so a\n * streamed reply keeps its row when its durable twin lands. Stays scrolled to the end\n * while the user is there and never steals focus; `data-status` mirrors the chat status.\n */\nexport function Thread({\n children,\n empty,\n older,\n ref,\n onScroll,\n ...props\n}: ChatThreadProps) {\n const { items, status, hasOlder } = useChatState(\"Chat.Thread\");\n const list = useRef<HTMLOListElement | null>(null);\n const atEnd = useRef(true);\n const anchor = useRef<{ readonly first?: string | undefined; readonly height: number }>(\n {\n height: 0,\n },\n );\n const attach = useCallback(\n (element: HTMLOListElement | null) => {\n list.current = element;\n if (typeof ref === \"function\") return ref(element);\n if (ref) {\n ref.current = element;\n return () => {\n ref.current = null;\n };\n }\n },\n [ref],\n );\n const track = (event: UIEvent<HTMLOListElement>) => {\n onScroll?.(event);\n const element = event.currentTarget;\n atEnd.current =\n element.scrollHeight - element.scrollTop - element.clientHeight <= NEAR_END_PX;\n };\n useLayoutEffect(() => {\n const element = list.current;\n if (!element) return;\n const first = items[0]?.id;\n const before = anchor.current;\n if (atEnd.current) element.scrollTop = element.scrollHeight;\n else if (\n before.first !== undefined &&\n first !== before.first &&\n items.some((item) => item.id === before.first)\n ) {\n element.scrollTop += element.scrollHeight - before.height;\n }\n anchor.current = { first, height: element.scrollHeight };\n });\n\n return (\n <Fragment>\n <ol role=\"log\" {...props} ref={attach} onScroll={track} data-status={status}>\n {hasOlder && older !== undefined ? <li data-kind=\"older\">{older}</li> : null}\n {items.map((item) => (\n <li\n key={item.id}\n data-item-id={item.id}\n data-kind={item.kind}\n data-streaming={\"streaming\" in item ? flag(item.streaming) : undefined}\n data-interrupted={\n item.kind === \"reply\" || item.kind === \"reasoning\"\n ? flag(item.interrupted)\n : undefined\n }\n >\n {children(item)}\n </li>\n ))}\n </ol>\n {items.length === 0 ? empty : null}\n </Fragment>\n );\n}\n\n/** `type=\"button\"`; rendered only while the thread has earlier items, which it reads. */\nexport function LoadOlder({ children, onClick, ...props }: ComponentProps<\"button\">) {\n const runtime = useChatRuntime(\"Chat.LoadOlder\");\n const { hasOlder, loadingOlder } = useChatState(\"Chat.LoadOlder\");\n if (!hasOlder) return null;\n return (\n <button\n type=\"button\"\n disabled={loadingOlder}\n {...props}\n onClick={(event) => {\n onClick?.(event);\n if (!event.defaultPrevented) {\n runtime.loadOlder().catch((error: unknown) => {\n console.error(\"[parlor] The chat could not do that:\", error);\n });\n }\n }}\n >\n {children ?? (loadingOlder ? \"Loading…\" : \"Show earlier messages\")}\n </button>\n );\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ChatRuntime, ChatState } from "./runtime.js";
|
|
2
|
+
export declare const ChatScope: import("react").Context<ChatRuntime | undefined>;
|
|
3
|
+
export declare function useChatRuntime(part: string): ChatRuntime;
|
|
4
|
+
export declare function useChatState(part: string): ChatState;
|
|
5
|
+
export declare function canSend(state: ChatState): boolean;
|
|
6
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/react/chat/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE3D,eAAO,MAAM,SAAS,kDAAoD,CAAC;AAE3E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAIxD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAGpD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAWjD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isPromptTooLong } from "@parlor/chat";
|
|
2
|
+
import { createContext, use, useSyncExternalStore } from "react";
|
|
3
|
+
export const ChatScope = createContext(undefined);
|
|
4
|
+
export function useChatRuntime(part) {
|
|
5
|
+
const runtime = use(ChatScope);
|
|
6
|
+
if (!runtime)
|
|
7
|
+
throw new Error(`${part} must be rendered inside Chat.Root.`);
|
|
8
|
+
return runtime;
|
|
9
|
+
}
|
|
10
|
+
export function useChatState(part) {
|
|
11
|
+
const runtime = useChatRuntime(part);
|
|
12
|
+
return useSyncExternalStore(runtime.subscribe, runtime.get, runtime.get);
|
|
13
|
+
}
|
|
14
|
+
export function canSend(state) {
|
|
15
|
+
const { agent } = state;
|
|
16
|
+
return (state.draft.trim() !== "" &&
|
|
17
|
+
state.setup === undefined &&
|
|
18
|
+
state.link.status === "linked" &&
|
|
19
|
+
!isPromptTooLong(state.draft) &&
|
|
20
|
+
agent !== undefined &&
|
|
21
|
+
agent.ready &&
|
|
22
|
+
(state.status === "idle" || state.status === "running" || state.status === "waiting"));
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/react/chat/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAIjE,MAAM,CAAC,MAAM,SAAS,GAAG,aAAa,CAA0B,SAAS,CAAC,CAAC;AAE3E,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/B,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,qCAAqC,CAAC,CAAC;IAC5E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,oBAAoB,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAgB;IACtC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACxB,OAAO,CACL,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QACzB,KAAK,CAAC,KAAK,KAAK,SAAS;QACzB,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ;QAC9B,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC;QAC7B,KAAK,KAAK,SAAS;QACnB,KAAK,CAAC,KAAK;QACX,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CACtF,CAAC;AACJ,CAAC","sourcesContent":["import { isPromptTooLong } from \"@parlor/chat\";\nimport { createContext, use, useSyncExternalStore } from \"react\";\n\nimport type { ChatRuntime, ChatState } from \"./runtime.js\";\n\nexport const ChatScope = createContext<ChatRuntime | undefined>(undefined);\n\nexport function useChatRuntime(part: string): ChatRuntime {\n const runtime = use(ChatScope);\n if (!runtime) throw new Error(`${part} must be rendered inside Chat.Root.`);\n return runtime;\n}\n\nexport function useChatState(part: string): ChatState {\n const runtime = useChatRuntime(part);\n return useSyncExternalStore(runtime.subscribe, runtime.get, runtime.get);\n}\n\nexport function canSend(state: ChatState): boolean {\n const { agent } = state;\n return (\n state.draft.trim() !== \"\" &&\n state.setup === undefined &&\n state.link.status === \"linked\" &&\n !isPromptTooLong(state.draft) &&\n agent !== undefined &&\n agent.ready &&\n (state.status === \"idle\" || state.status === \"running\" || state.status === \"waiting\")\n );\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentUiClient } from "@parlor/agent-ui/client";
|
|
2
|
+
import { type ActionClient } from "../../action-client.js";
|
|
3
|
+
import type { RegisteredActions } from "../../register.js";
|
|
4
|
+
import type { FrameworkAuth } from "../../session.js";
|
|
5
|
+
import type { FrameworkSync } from "../sync-bridge.js";
|
|
6
|
+
import { type ChatRuntime } from "./runtime.js";
|
|
7
|
+
export interface RootRuntimeInput {
|
|
8
|
+
readonly client: ActionClient<RegisteredActions>;
|
|
9
|
+
readonly agentUi: AgentUiClient;
|
|
10
|
+
readonly auth: FrameworkAuth | undefined;
|
|
11
|
+
readonly sync: FrameworkSync;
|
|
12
|
+
readonly threadId: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare function createRootRuntime(input: RootRuntimeInput): ChatRuntime;
|
|
15
|
+
//# sourceMappingURL=create.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/react/chat/create.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAI7D,OAAO,EAAE,KAAK,YAAY,EAAa,MAAM,wBAAwB,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,cAAc,CAAC;AAEnE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,iBAAiB,CAAC,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAkBtE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { localAgentHost, resolveCapability } from "@parlor/capabilities";
|
|
2
|
+
import { appOrigin } from "../../action-client.js";
|
|
3
|
+
import { createPluginClient } from "../../plugin-client.js";
|
|
4
|
+
import { createChatRuntime } from "./runtime.js";
|
|
5
|
+
export function createRootRuntime(input) {
|
|
6
|
+
const { client, agentUi, auth, sync, threadId } = input;
|
|
7
|
+
return createChatRuntime({
|
|
8
|
+
calls: createPluginClient(client, "chat"),
|
|
9
|
+
clientId: client.clientId,
|
|
10
|
+
appOrigin: appOrigin(),
|
|
11
|
+
agentUi,
|
|
12
|
+
addChannel: (channel) => sync.addChannel(channel),
|
|
13
|
+
onSyncTags: (listener) => sync.onEvents((events) => listener(events.flatMap((event) => event.tags))),
|
|
14
|
+
onResume: (listener) => sync.onResume(listener),
|
|
15
|
+
transport: () => sync.transport(),
|
|
16
|
+
onNegotiated: (listener) => sync.onNegotiated(listener),
|
|
17
|
+
isHidden: () => globalThis.document?.visibilityState === "hidden",
|
|
18
|
+
auth,
|
|
19
|
+
localHost: resolveCapability(localAgentHost),
|
|
20
|
+
threadId,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/react/chat/create.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzE,OAAO,EAAqB,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAI5D,OAAO,EAAoB,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAUnE,MAAM,UAAU,iBAAiB,CAAC,KAAuB;IACvD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACxD,OAAO,iBAAiB,CAAC;QACvB,KAAK,EAAE,kBAAkB,CAAoB,MAAM,EAAE,MAAM,CAAC;QAC5D,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,SAAS,EAAE,SAAS,EAAE;QACtB,OAAO;QACP,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QACjD,UAAU,EAAE,CAAC,QAAQ,EAAE,EAAE,CACvB,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5E,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC/C,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE;QACjC,YAAY,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC;QACvD,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,eAAe,KAAK,QAAQ;QACjE,IAAI;QACJ,SAAS,EAAE,iBAAiB,CAAC,cAAc,CAAC;QAC5C,QAAQ;KACT,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { AgentUiClient } from \"@parlor/agent-ui/client\";\nimport { localAgentHost, resolveCapability } from \"@parlor/capabilities\";\nimport type { ChatUserActionMap } from \"@parlor/chat/server\";\n\nimport { type ActionClient, appOrigin } from \"../../action-client.js\";\nimport { createPluginClient } from \"../../plugin-client.js\";\nimport type { RegisteredActions } from \"../../register.js\";\nimport type { FrameworkAuth } from \"../../session.js\";\nimport type { FrameworkSync } from \"../sync-bridge.js\";\nimport { type ChatRuntime, createChatRuntime } from \"./runtime.js\";\n\nexport interface RootRuntimeInput {\n readonly client: ActionClient<RegisteredActions>;\n readonly agentUi: AgentUiClient;\n readonly auth: FrameworkAuth | undefined;\n readonly sync: FrameworkSync;\n readonly threadId: string | undefined;\n}\n\nexport function createRootRuntime(input: RootRuntimeInput): ChatRuntime {\n const { client, agentUi, auth, sync, threadId } = input;\n return createChatRuntime({\n calls: createPluginClient<ChatUserActionMap>(client, \"chat\"),\n clientId: client.clientId,\n appOrigin: appOrigin(),\n agentUi,\n addChannel: (channel) => sync.addChannel(channel),\n onSyncTags: (listener) =>\n sync.onEvents((events) => listener(events.flatMap((event) => event.tags))),\n onResume: (listener) => sync.onResume(listener),\n transport: () => sync.transport(),\n onNegotiated: (listener) => sync.onNegotiated(listener),\n isHidden: () => globalThis.document?.visibilityState === \"hidden\",\n auth,\n localHost: resolveCapability(localAgentHost),\n threadId,\n });\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ChatFrame, type LoggedItem, type OpenTurn, type TurnStatus } from "@parlor/chat";
|
|
2
|
+
import type { TurnId } from "@parlor/core";
|
|
3
|
+
export interface OpenTurns {
|
|
4
|
+
readonly turns: ReadonlyMap<TurnId, OpenTurn>;
|
|
5
|
+
}
|
|
6
|
+
export declare const NO_OPEN_TURNS: OpenTurns;
|
|
7
|
+
export declare function applyFrame(open: OpenTurns, frame: ChatFrame): OpenTurns;
|
|
8
|
+
export declare function mergeSnapshot(open: OpenTurns, snapshot: readonly OpenTurn[]): OpenTurns;
|
|
9
|
+
export declare function pruneOpen(open: OpenTurns, durable: ReadonlyMap<string, LoggedItem>, statuses: readonly TurnStatus[]): OpenTurns;
|
|
10
|
+
//# sourceMappingURL=open-turns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-turns.d.ts","sourceRoot":"","sources":["../../../src/react/chat/open-turns.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,UAAU,EAEf,KAAK,QAAQ,EACb,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CAC/C;AAED,eAAO,MAAM,aAAa,EAAE,SAAgC,CAAC;AAE7D,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,CAYvE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,CA2BvF;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,EACxC,QAAQ,EAAE,SAAS,UAAU,EAAE,GAC9B,SAAS,CAaX"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { applyFrame as applyToItems, } from "@parlor/chat";
|
|
2
|
+
export const NO_OPEN_TURNS = { turns: new Map() };
|
|
3
|
+
export function applyFrame(open, frame) {
|
|
4
|
+
if (frame.type === "settled")
|
|
5
|
+
return open;
|
|
6
|
+
const turn = open.turns.get(frame.turnId);
|
|
7
|
+
const applied = applyToItems(turn?.items ?? [], frame);
|
|
8
|
+
if (!applied)
|
|
9
|
+
return open;
|
|
10
|
+
const next = new Map(open.turns);
|
|
11
|
+
next.set(frame.turnId, {
|
|
12
|
+
turnId: frame.turnId,
|
|
13
|
+
threadId: frame.threadId,
|
|
14
|
+
items: [...applied.items],
|
|
15
|
+
});
|
|
16
|
+
return { turns: next };
|
|
17
|
+
}
|
|
18
|
+
export function mergeSnapshot(open, snapshot) {
|
|
19
|
+
const turns = new Map(snapshot.map((turn) => [turn.turnId, turn]));
|
|
20
|
+
for (const [turnId, local] of open.turns) {
|
|
21
|
+
const remote = turns.get(turnId);
|
|
22
|
+
if (!remote) {
|
|
23
|
+
turns.set(turnId, local);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const longer = (item) => {
|
|
27
|
+
const mine = local.items.find((candidate) => candidate.id === item.id);
|
|
28
|
+
return mine &&
|
|
29
|
+
"text" in mine &&
|
|
30
|
+
"text" in item &&
|
|
31
|
+
mine.text.length > item.text.length
|
|
32
|
+
? mine
|
|
33
|
+
: item;
|
|
34
|
+
};
|
|
35
|
+
const known = new Set(remote.items.map((item) => item.id));
|
|
36
|
+
turns.set(turnId, {
|
|
37
|
+
...remote,
|
|
38
|
+
items: [
|
|
39
|
+
...remote.items.map(longer),
|
|
40
|
+
...local.items.filter((i) => !known.has(i.id)),
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return { turns };
|
|
45
|
+
}
|
|
46
|
+
export function pruneOpen(open, durable, statuses) {
|
|
47
|
+
const over = new Set(statuses
|
|
48
|
+
.filter((s) => s.state !== "queued" && s.state !== "running")
|
|
49
|
+
.map((s) => s.turnId));
|
|
50
|
+
const turns = new Map();
|
|
51
|
+
for (const [turnId, turn] of open.turns) {
|
|
52
|
+
if (over.has(turnId))
|
|
53
|
+
continue;
|
|
54
|
+
const items = turn.items.filter((item) => !durable.has(item.id));
|
|
55
|
+
if (items.length > 0)
|
|
56
|
+
turns.set(turnId, { ...turn, items });
|
|
57
|
+
}
|
|
58
|
+
return { turns };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=open-turns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-turns.js","sourceRoot":"","sources":["../../../src/react/chat/open-turns.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n applyFrame as applyToItems,\n type ChatFrame,\n type LoggedItem,\n type OpenItem,\n type OpenTurn,\n type TurnStatus,\n} from \"@parlor/chat\";\nimport type { TurnId } from \"@parlor/core\";\n\nexport interface OpenTurns {\n readonly turns: ReadonlyMap<TurnId, OpenTurn>;\n}\n\nexport const NO_OPEN_TURNS: OpenTurns = { turns: new Map() };\n\nexport function applyFrame(open: OpenTurns, frame: ChatFrame): OpenTurns {\n if (frame.type === \"settled\") return open;\n const turn = open.turns.get(frame.turnId);\n const applied = applyToItems(turn?.items ?? [], frame);\n if (!applied) return open;\n const next = new Map(open.turns);\n next.set(frame.turnId, {\n turnId: frame.turnId,\n threadId: frame.threadId,\n items: [...applied.items],\n });\n return { turns: next };\n}\n\nexport function mergeSnapshot(open: OpenTurns, snapshot: readonly OpenTurn[]): OpenTurns {\n const turns = new Map<TurnId, OpenTurn>(snapshot.map((turn) => [turn.turnId, turn]));\n for (const [turnId, local] of open.turns) {\n const remote = turns.get(turnId);\n if (!remote) {\n turns.set(turnId, local);\n continue;\n }\n const longer = (item: OpenItem): OpenItem => {\n const mine = local.items.find((candidate) => candidate.id === item.id);\n return mine &&\n \"text\" in mine &&\n \"text\" in item &&\n mine.text.length > item.text.length\n ? mine\n : item;\n };\n const known = new Set(remote.items.map((item) => item.id));\n turns.set(turnId, {\n ...remote,\n items: [\n ...remote.items.map(longer),\n ...local.items.filter((i) => !known.has(i.id)),\n ],\n });\n }\n return { turns };\n}\n\nexport function pruneOpen(\n open: OpenTurns,\n durable: ReadonlyMap<string, LoggedItem>,\n statuses: readonly TurnStatus[],\n): OpenTurns {\n const over = new Set(\n statuses\n .filter((s) => s.state !== \"queued\" && s.state !== \"running\")\n .map((s) => s.turnId),\n );\n const turns = new Map<TurnId, OpenTurn>();\n for (const [turnId, turn] of open.turns) {\n if (over.has(turnId)) continue;\n const items = turn.items.filter((item) => !durable.has(item.id));\n if (items.length > 0) turns.set(turnId, { ...turn, items });\n }\n return { turns };\n}\n"]}
|