@steerable/agent-ui 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 +91 -0
- package/README.md +118 -0
- package/dist/components/ChatPanel.d.ts +42 -0
- package/dist/components/ChatPanel.d.ts.map +1 -0
- package/dist/components/ChatPanel.js +46 -0
- package/dist/components/ChatPanel.js.map +1 -0
- package/dist/components/ChatPanel.stories.d.ts +17 -0
- package/dist/components/ChatPanel.stories.d.ts.map +1 -0
- package/dist/components/ChatPanel.stories.js +143 -0
- package/dist/components/ChatPanel.stories.js.map +1 -0
- package/dist/components/MessageList.d.ts +44 -0
- package/dist/components/MessageList.d.ts.map +1 -0
- package/dist/components/MessageList.js +43 -0
- package/dist/components/MessageList.js.map +1 -0
- package/dist/components/MessageList.stories.d.ts +12 -0
- package/dist/components/MessageList.stories.d.ts.map +1 -0
- package/dist/components/MessageList.stories.js +88 -0
- package/dist/components/MessageList.stories.js.map +1 -0
- package/dist/components/OrchestrationPlanCard.d.ts +32 -0
- package/dist/components/OrchestrationPlanCard.d.ts.map +1 -0
- package/dist/components/OrchestrationPlanCard.js +48 -0
- package/dist/components/OrchestrationPlanCard.js.map +1 -0
- package/dist/components/OrchestrationPlanCard.stories.d.ts +12 -0
- package/dist/components/OrchestrationPlanCard.stories.d.ts.map +1 -0
- package/dist/components/OrchestrationPlanCard.stories.js +84 -0
- package/dist/components/OrchestrationPlanCard.stories.js.map +1 -0
- package/dist/components/SSEStreamView.d.ts +29 -0
- package/dist/components/SSEStreamView.d.ts.map +1 -0
- package/dist/components/SSEStreamView.js +78 -0
- package/dist/components/SSEStreamView.js.map +1 -0
- package/dist/components/SSEStreamView.stories.d.ts +12 -0
- package/dist/components/SSEStreamView.stories.d.ts.map +1 -0
- package/dist/components/SSEStreamView.stories.js +86 -0
- package/dist/components/SSEStreamView.stories.js.map +1 -0
- package/dist/components/ToolCallRenderer.d.ts +42 -0
- package/dist/components/ToolCallRenderer.d.ts.map +1 -0
- package/dist/components/ToolCallRenderer.js +70 -0
- package/dist/components/ToolCallRenderer.js.map +1 -0
- package/dist/components/ToolCallRenderer.stories.d.ts +13 -0
- package/dist/components/ToolCallRenderer.stories.d.ts.map +1 -0
- package/dist/components/ToolCallRenderer.stories.js +112 -0
- package/dist/components/ToolCallRenderer.stories.js.map +1 -0
- package/dist/components/cn.d.ts +7 -0
- package/dist/components/cn.d.ts.map +1 -0
- package/dist/components/cn.js +9 -0
- package/dist/components/cn.js.map +1 -0
- package/dist/components/index.d.ts +11 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useAgentSession.d.ts +56 -0
- package/dist/hooks/useAgentSession.d.ts.map +1 -0
- package/dist/hooks/useAgentSession.js +128 -0
- package/dist/hooks/useAgentSession.js.map +1 -0
- package/dist/hooks/useChatStream.d.ts +59 -0
- package/dist/hooks/useChatStream.d.ts.map +1 -0
- package/dist/hooks/useChatStream.js +251 -0
- package/dist/hooks/useChatStream.js.map +1 -0
- package/dist/hooks/useToolCallStatus.d.ts +47 -0
- package/dist/hooks/useToolCallStatus.d.ts.map +1 -0
- package/dist/hooks/useToolCallStatus.js +57 -0
- package/dist/hooks/useToolCallStatus.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/tailwind-preset.d.ts +76 -0
- package/dist/tailwind-preset.d.ts.map +1 -0
- package/dist/tailwind-preset.js +104 -0
- package/dist/tailwind-preset.js.map +1 -0
- package/package.json +97 -0
- package/src/Introduction.mdx +71 -0
- package/src/components/ChatPanel.stories.tsx +173 -0
- package/src/components/ChatPanel.tsx +188 -0
- package/src/components/MessageList.stories.tsx +112 -0
- package/src/components/MessageList.test.tsx +57 -0
- package/src/components/MessageList.tsx +144 -0
- package/src/components/OrchestrationPlanCard.stories.tsx +98 -0
- package/src/components/OrchestrationPlanCard.test.tsx +34 -0
- package/src/components/OrchestrationPlanCard.tsx +159 -0
- package/src/components/SSEStreamView.stories.tsx +99 -0
- package/src/components/SSEStreamView.test.tsx +42 -0
- package/src/components/SSEStreamView.tsx +155 -0
- package/src/components/ToolCallRenderer.stories.tsx +152 -0
- package/src/components/ToolCallRenderer.test.tsx +93 -0
- package/src/components/ToolCallRenderer.tsx +213 -0
- package/src/components/cn.ts +10 -0
- package/src/components/index.ts +18 -0
- package/src/hooks/index.ts +21 -0
- package/src/hooks/useAgentSession.mdx +115 -0
- package/src/hooks/useAgentSession.test.ts +127 -0
- package/src/hooks/useAgentSession.ts +181 -0
- package/src/hooks/useChatStream.mdx +129 -0
- package/src/hooks/useChatStream.test.ts +233 -0
- package/src/hooks/useChatStream.ts +341 -0
- package/src/hooks/useToolCallStatus.mdx +102 -0
- package/src/hooks/useToolCallStatus.test.ts +99 -0
- package/src/hooks/useToolCallStatus.ts +99 -0
- package/src/index.ts +12 -0
- package/src/tailwind-preset.ts +109 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity.
|
|
18
|
+
|
|
19
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
20
|
+
exercising permissions granted by this License.
|
|
21
|
+
|
|
22
|
+
"Source" form shall mean the preferred form for making modifications.
|
|
23
|
+
|
|
24
|
+
"Object" form shall mean any form resulting from mechanical
|
|
25
|
+
transformation or translation of a Source form.
|
|
26
|
+
|
|
27
|
+
"Work" shall mean the work of authorship made available under the
|
|
28
|
+
License.
|
|
29
|
+
|
|
30
|
+
"Derivative Works" shall mean any work based on the Work.
|
|
31
|
+
|
|
32
|
+
"Contribution" shall mean any work of authorship intentionally
|
|
33
|
+
submitted to Licensor for inclusion in the Work.
|
|
34
|
+
|
|
35
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
36
|
+
on behalf of whom a Contribution has been received by Licensor.
|
|
37
|
+
|
|
38
|
+
2. Grant of Copyright License.
|
|
39
|
+
Subject to the terms and conditions of this License, each Contributor
|
|
40
|
+
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
|
41
|
+
royalty-free, irrevocable copyright license to reproduce, prepare
|
|
42
|
+
Derivative Works of, publicly display, publicly perform, sublicense,
|
|
43
|
+
and distribute the Work and such Derivative Works in Source or Object
|
|
44
|
+
form.
|
|
45
|
+
|
|
46
|
+
3. Grant of Patent License.
|
|
47
|
+
Subject to the terms and conditions of this License, each Contributor
|
|
48
|
+
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
|
49
|
+
royalty-free, irrevocable patent license to make, have made, use,
|
|
50
|
+
offer to sell, sell, import, and otherwise transfer the Work.
|
|
51
|
+
|
|
52
|
+
4. Redistribution.
|
|
53
|
+
You may reproduce and distribute copies of the Work or Derivative
|
|
54
|
+
Works thereof in any medium, with or without modifications, and in
|
|
55
|
+
Source or Object form, provided that You meet the following conditions:
|
|
56
|
+
|
|
57
|
+
(a) You must give any other recipients of the Work or Derivative
|
|
58
|
+
Works a copy of this License; and
|
|
59
|
+
(b) You must cause any modified files to carry prominent notices
|
|
60
|
+
stating that You changed the files; and
|
|
61
|
+
(c) You must retain, in the Source form of any Derivative Works that
|
|
62
|
+
You distribute, all copyright, patent, trademark, and attribution
|
|
63
|
+
notices from the Source form of the Work; and
|
|
64
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
65
|
+
distribution, then any Derivative Works that You distribute must
|
|
66
|
+
include a readable copy of the attribution notices contained within
|
|
67
|
+
such NOTICE file.
|
|
68
|
+
|
|
69
|
+
5. Submission of Contributions.
|
|
70
|
+
Unless You explicitly state otherwise, any Contribution intentionally
|
|
71
|
+
submitted for inclusion in the Work shall be under the terms and
|
|
72
|
+
conditions of this License.
|
|
73
|
+
|
|
74
|
+
6. Trademarks.
|
|
75
|
+
This License does not grant permission to use the trade names,
|
|
76
|
+
trademarks, service marks, or product names of the Licensor.
|
|
77
|
+
|
|
78
|
+
7. Disclaimer of Warranty.
|
|
79
|
+
Unless required by applicable law or agreed to in writing, Licensor
|
|
80
|
+
provides the Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
|
81
|
+
OF ANY KIND.
|
|
82
|
+
|
|
83
|
+
8. Limitation of Liability.
|
|
84
|
+
In no event and under no legal theory shall any Contributor be liable
|
|
85
|
+
to You for damages arising as a result of this License or out of the
|
|
86
|
+
use or inability to use the Work.
|
|
87
|
+
|
|
88
|
+
9. Accepting Warranty or Additional Liability.
|
|
89
|
+
While redistributing the Work or Derivative Works thereof, You may
|
|
90
|
+
choose to offer, and charge a fee for, acceptance of support, warranty,
|
|
91
|
+
indemnity, or other liability obligations.
|
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @steerable/agent-ui
|
|
2
|
+
|
|
3
|
+
> Headless React components + hooks + Tailwind preset for building agent UIs on
|
|
4
|
+
> top of `@steerable/agent-protocol`. Drop-in compatible with `deeppath/apps/web`'s
|
|
5
|
+
> ChatPanel architecture; designed to be reused by `deeppath-agent` (Electron)
|
|
6
|
+
> after sidecar wiring lands.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @steerable/agent-ui @steerable/agent-protocol react react-dom
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`@steerable/agent-ui` ships only **types + hooks + headless components**. UI
|
|
15
|
+
styling is delegated to your Tailwind config via the `@steerable/agent-ui`
|
|
16
|
+
preset.
|
|
17
|
+
|
|
18
|
+
## Tailwind preset
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// tailwind.config.ts
|
|
22
|
+
import preset from '@steerable/agent-ui/tailwind-preset';
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
presets: [preset],
|
|
26
|
+
content: [
|
|
27
|
+
'./src/**/*.{ts,tsx}',
|
|
28
|
+
'./node_modules/@steerable/agent-ui/dist/**/*.js',
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The preset exposes design tokens (`bg-agent-canvas`, `text-agent-foreground`,
|
|
34
|
+
`border-agent-border`, …) that map to CSS variables. Override them in your own
|
|
35
|
+
`globals.css` to retheme without forking components.
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
@steerable/agent-protocol (types: SSEEvent, ChatMessage, ToolCall, ...)
|
|
41
|
+
▲
|
|
42
|
+
│ types-only
|
|
43
|
+
│
|
|
44
|
+
@steerable/agent-ui
|
|
45
|
+
├─ hooks/ ← framework-agnostic state + side-effects
|
|
46
|
+
│ ├─ useAgentSession create / resume / list sessions
|
|
47
|
+
│ ├─ useChatStream consume an SSE stream of events into ChatMessage[]
|
|
48
|
+
│ └─ useToolCallStatus tool call lifecycle (proposed → running → done)
|
|
49
|
+
│
|
|
50
|
+
├─ components/ ← headless React (Tailwind class names only)
|
|
51
|
+
│ ├─ MessageList virtualized + auto-scroll list of ChatMessages
|
|
52
|
+
│ ├─ ChatPanel orchestrates header / list / input
|
|
53
|
+
│ ├─ OrchestrationPlanCard multi-step plan with progress
|
|
54
|
+
│ ├─ ToolCallRenderer safe (read) / proposed (write) / running / done
|
|
55
|
+
│ └─ SSEStreamView live JSON event log (debug + power-user)
|
|
56
|
+
│
|
|
57
|
+
└─ tailwind-preset.ts ← shared design tokens
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each hook returns plain data; each component takes plain props. Nothing imports
|
|
61
|
+
React Router, Next.js, or any specific HTTP client — the consumer wires the
|
|
62
|
+
actual transport (fetch / SSE / IPC / sidecar).
|
|
63
|
+
|
|
64
|
+
## Status (0.1.0)
|
|
65
|
+
|
|
66
|
+
This is the **foundation release** of the framework UI. It ships:
|
|
67
|
+
|
|
68
|
+
* ✅ Tailwind preset with the design tokens used by `deeppath/apps/web`.
|
|
69
|
+
* ✅ Three foundational hooks listed in the architecture diagram.
|
|
70
|
+
* ✅ Five core components listed in the architecture diagram (intentionally
|
|
71
|
+
the minimum for a usable chat UI). Each is fully type-safe against
|
|
72
|
+
`@steerable/agent-protocol`.
|
|
73
|
+
* ✅ Vitest test suite covering hooks + render snapshots.
|
|
74
|
+
* ⏸ Storybook is **deferred** to 0.2.0 / P8 docs (per project decision).
|
|
75
|
+
* ⏸ Multi-agent group chat (ChatTabs / AgentManagementModal), slash command
|
|
76
|
+
palette, automation alerts, file upload, share-image modal, etc. are tracked
|
|
77
|
+
for 0.2.0 / 0.3.0 — they exist in `deeppath/apps/web` today and will be
|
|
78
|
+
ported once the foundational set is consumed in production by P7.
|
|
79
|
+
|
|
80
|
+
## Usage example
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import {
|
|
84
|
+
ChatPanel,
|
|
85
|
+
MessageList,
|
|
86
|
+
ToolCallRenderer,
|
|
87
|
+
useChatStream,
|
|
88
|
+
} from '@steerable/agent-ui';
|
|
89
|
+
import type { SSEEvent } from '@steerable/agent-protocol';
|
|
90
|
+
|
|
91
|
+
function MyChat({ chatId }: { chatId: string }) {
|
|
92
|
+
const { messages, isStreaming, sendUserMessage } = useChatStream({
|
|
93
|
+
transport: {
|
|
94
|
+
stream: async (input, onEvent) => {
|
|
95
|
+
const res = await fetch('/api/v2/chats/' + chatId + '/stream', {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
body: JSON.stringify(input),
|
|
98
|
+
});
|
|
99
|
+
const reader = res.body!.getReader();
|
|
100
|
+
// … decode + dispatch onEvent(SSEEvent) …
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<ChatPanel
|
|
107
|
+
messages={messages}
|
|
108
|
+
isStreaming={isStreaming}
|
|
109
|
+
onSubmit={sendUserMessage}
|
|
110
|
+
renderToolCall={(call) => <ToolCallRenderer call={call} />}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
See `apps/web/src/app/goals/desktop/components/ChatPanel/ChatPanel.tsx` in the
|
|
117
|
+
`deeppath` repo for the production-grade caller (which P7 will rewrite to
|
|
118
|
+
import these primitives).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ChatPanel` — the foundational orchestration shell.
|
|
3
|
+
*
|
|
4
|
+
* Composes `MessageList` + a minimal input row + a stop button. The component
|
|
5
|
+
* is intentionally minimal so consumers can layer on the production-grade
|
|
6
|
+
* extras (model selector, tool toggles, mention picker, slash commands, file
|
|
7
|
+
* upload, automation alerts, share-image button, multi-tab agent switcher,
|
|
8
|
+
* orchestration sidebar) without having to fork the chat itself.
|
|
9
|
+
*
|
|
10
|
+
* Production callers in `deeppath/apps/web` will build their fancier
|
|
11
|
+
* `<ChatHeader />` and `<ChatInput />` separately and feed `ChatPanel` only
|
|
12
|
+
* the `messages` + `onSubmit` props — the rest is rendered around it.
|
|
13
|
+
*/
|
|
14
|
+
import type { ChatMessage, ToolCall, ToolResult } from '@steerable/agent-protocol';
|
|
15
|
+
export interface ChatPanelProps {
|
|
16
|
+
messages: ChatMessage[];
|
|
17
|
+
isStreaming?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Called when the user submits the input. Receives the trimmed string.
|
|
20
|
+
* The parent is responsible for calling `useChatStream().sendUserMessage`.
|
|
21
|
+
*/
|
|
22
|
+
onSubmit: (input: {
|
|
23
|
+
content: string;
|
|
24
|
+
}) => void | Promise<void>;
|
|
25
|
+
onCancel?: () => void;
|
|
26
|
+
className?: string;
|
|
27
|
+
inputPlaceholder?: string;
|
|
28
|
+
/** Slot rendered above the message list (header, agent picker, …). */
|
|
29
|
+
header?: React.ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* Tool-call renderer. Default uses the framework's `ToolCallRenderer`;
|
|
32
|
+
* pass `() => null` to suppress, or your own component to inject a
|
|
33
|
+
* domain-specific renderer (CIFLog cards, SQL query previews, …).
|
|
34
|
+
*/
|
|
35
|
+
renderToolCall?: (call: ToolCall, result?: ToolResult) => React.ReactNode;
|
|
36
|
+
/** Empty-state slot for the message list. */
|
|
37
|
+
emptyState?: React.ReactNode;
|
|
38
|
+
/** Disable the input (e.g. during initial chat load). */
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function ChatPanel(props: ChatPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
//# sourceMappingURL=ChatPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatPanel.d.ts","sourceRoot":"","sources":["../../src/components/ChatPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAKnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,UAAU,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1E,6CAA6C;IAC7C,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAMD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,2CA0I9C"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `ChatPanel` — the foundational orchestration shell.
|
|
4
|
+
*
|
|
5
|
+
* Composes `MessageList` + a minimal input row + a stop button. The component
|
|
6
|
+
* is intentionally minimal so consumers can layer on the production-grade
|
|
7
|
+
* extras (model selector, tool toggles, mention picker, slash commands, file
|
|
8
|
+
* upload, automation alerts, share-image button, multi-tab agent switcher,
|
|
9
|
+
* orchestration sidebar) without having to fork the chat itself.
|
|
10
|
+
*
|
|
11
|
+
* Production callers in `deeppath/apps/web` will build their fancier
|
|
12
|
+
* `<ChatHeader />` and `<ChatInput />` separately and feed `ChatPanel` only
|
|
13
|
+
* the `messages` + `onSubmit` props — the rest is rendered around it.
|
|
14
|
+
*/
|
|
15
|
+
import { useCallback, useState } from 'react';
|
|
16
|
+
import { MessageList } from './MessageList.js';
|
|
17
|
+
import { ToolCallRenderer } from './ToolCallRenderer.js';
|
|
18
|
+
import { cn } from './cn.js';
|
|
19
|
+
function defaultRenderToolCall(call, result) {
|
|
20
|
+
return _jsx(ToolCallRenderer, { call: call, result: result }, call.id);
|
|
21
|
+
}
|
|
22
|
+
export function ChatPanel(props) {
|
|
23
|
+
const { messages, isStreaming = false, onSubmit, onCancel, className, inputPlaceholder = 'Send a message…', header, renderToolCall = defaultRenderToolCall, emptyState, disabled = false, } = props;
|
|
24
|
+
const [draft, setDraft] = useState('');
|
|
25
|
+
const handleSubmit = useCallback(async (e) => {
|
|
26
|
+
e?.preventDefault();
|
|
27
|
+
const trimmed = draft.trim();
|
|
28
|
+
if (!trimmed || disabled)
|
|
29
|
+
return;
|
|
30
|
+
setDraft('');
|
|
31
|
+
await onSubmit({ content: trimmed });
|
|
32
|
+
}, [draft, disabled, onSubmit]);
|
|
33
|
+
const renderMessage = useCallback((rendererProps) => {
|
|
34
|
+
const { message, isLastAssistant, isStreaming: streaming } = rendererProps;
|
|
35
|
+
const isAssistant = message.role === 'assistant';
|
|
36
|
+
return (_jsx("div", { "data-role": message.role, className: cn('flex w-full', isAssistant ? 'justify-start' : 'justify-end'), children: _jsxs("div", { className: cn('flex max-w-[80%] flex-col gap-2', isAssistant ? 'items-start' : 'items-end'), children: [_jsxs("div", { className: cn('rounded-agent-md border px-3 py-2 text-sm leading-relaxed', isAssistant
|
|
37
|
+
? 'border-agent-border bg-agent-muted text-agent-foreground'
|
|
38
|
+
: 'border-transparent bg-agent-accent text-agent-accent-foreground'), children: [_jsx("div", { className: "whitespace-pre-wrap", children: message.content || (isLastAssistant && streaming ? '…' : '') }), isLastAssistant && streaming ? (_jsx("span", { "aria-hidden": true, className: "ml-0.5 inline-block h-3 w-1.5 align-baseline bg-agent-foreground animate-agent-cursor-blink" })) : null] }), Array.isArray(message.toolCalls) && message.toolCalls.length > 0 ? (_jsx("div", { className: "flex w-full flex-col gap-1.5", children: message.toolCalls.map((call) => renderToolCall(call,
|
|
39
|
+
// The hook attaches a single `toolResult` to the message;
|
|
40
|
+
// production callers that want per-call results should
|
|
41
|
+
// override `renderToolCall` and look them up themselves.
|
|
42
|
+
message.toolResult)) })) : null] }) }, message.id ?? rendererProps.index));
|
|
43
|
+
}, [renderToolCall]);
|
|
44
|
+
return (_jsxs("div", { className: cn('flex h-full w-full flex-col bg-agent-canvas text-agent-foreground', className), children: [header, _jsx("div", { className: "flex-1 overflow-hidden", children: _jsx(MessageList, { messages: messages, isStreaming: isStreaming, renderMessage: renderMessage, emptyState: emptyState }) }), _jsxs("form", { className: "flex items-center gap-2 border-t border-agent-border bg-agent-canvas px-3 py-2", onSubmit: handleSubmit, children: [_jsx("input", { type: "text", value: draft, onChange: (e) => setDraft(e.target.value), placeholder: inputPlaceholder, disabled: disabled, className: cn('flex-1 rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-sm text-agent-foreground placeholder:text-agent-muted-foreground', 'focus:border-agent-accent focus:outline-none focus:ring-1 focus:ring-agent-accent', disabled && 'opacity-50') }), isStreaming && onCancel ? (_jsx("button", { type: "button", onClick: onCancel, className: "rounded-agent-md border border-agent-border px-3 py-2 text-sm text-agent-foreground hover:bg-agent-muted", children: "Stop" })) : (_jsx("button", { type: "submit", disabled: disabled || !draft.trim(), className: cn('rounded-agent-md bg-agent-accent px-3 py-2 text-sm font-medium text-agent-accent-foreground', (disabled || !draft.trim()) && 'opacity-50'), children: "Send" }))] })] }));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=ChatPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatPanel.js","sourceRoot":"","sources":["../../src/components/ChatPanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ChatPanel } from './ChatPanel.js';
|
|
3
|
+
declare const meta: Meta<typeof ChatPanel>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof ChatPanel>;
|
|
6
|
+
export declare const Empty: Story;
|
|
7
|
+
export declare const WithMessages: Story;
|
|
8
|
+
export declare const Streaming: Story;
|
|
9
|
+
export declare const Disabled: Story;
|
|
10
|
+
export declare const WithToolCalls: Story;
|
|
11
|
+
/**
|
|
12
|
+
* Demonstrates the full controlled pattern: parent owns the message list,
|
|
13
|
+
* appends a new turn on submit, and toggles `isStreaming` while the assistant
|
|
14
|
+
* is "responding". Useful for sandbox-testing custom renderers.
|
|
15
|
+
*/
|
|
16
|
+
export declare const Interactive: Story;
|
|
17
|
+
//# sourceMappingURL=ChatPanel.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatPanel.stories.d.ts","sourceRoot":"","sources":["../../src/components/ChatPanel.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,SAAS,CAwBhC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,SAAS,CAAC,CAAC;AA0BxC,eAAO,MAAM,KAAK,EAAE,KAUnB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAI1B,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KAavB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAMtB,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KA6B3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,KAqCzB,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { ChatPanel } from './ChatPanel.js';
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Components/ChatPanel',
|
|
6
|
+
component: ChatPanel,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'fullscreen',
|
|
9
|
+
actions: { argTypesRegex: '^on[A-Z].*' },
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: 'Foundational orchestration shell. Renders a `MessageList` plus a minimal input row and a stop button. Production callers typically wrap it with their own header/composer and only feed in `messages` + `onSubmit`.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
args: {
|
|
17
|
+
onSubmit: () => { },
|
|
18
|
+
inputPlaceholder: 'Send a message…',
|
|
19
|
+
},
|
|
20
|
+
decorators: [
|
|
21
|
+
(Story) => (_jsx("div", { className: "h-[640px] w-full max-w-3xl border border-agent-border bg-agent-canvas", children: _jsx(Story, {}) })),
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
export default meta;
|
|
25
|
+
const NOW = '2026-05-15T00:00:00Z';
|
|
26
|
+
const sampleMessages = [
|
|
27
|
+
{
|
|
28
|
+
id: 'm-1',
|
|
29
|
+
role: 'user',
|
|
30
|
+
content: 'Plan a 3-day trip to Tokyo for a first-time visitor.',
|
|
31
|
+
createdAt: NOW,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'm-2',
|
|
35
|
+
role: 'assistant',
|
|
36
|
+
content: "Sure — here's a balanced 3-day plan covering culture, food and a half-day trip outside the city. I'll list each day with morning/afternoon/evening blocks.",
|
|
37
|
+
createdAt: NOW,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'm-3',
|
|
41
|
+
role: 'user',
|
|
42
|
+
content: 'Great. Also include a vegetarian dinner each night.',
|
|
43
|
+
createdAt: NOW,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
export const Empty = {
|
|
47
|
+
args: {
|
|
48
|
+
messages: [],
|
|
49
|
+
emptyState: (_jsxs("div", { className: "m-auto max-w-sm text-center text-sm text-agent-muted-foreground", children: [_jsx("p", { className: "font-medium text-agent-foreground", children: "Start a new conversation" }), _jsx("p", { className: "mt-1", children: "Ask anything \u2014 I'll plan, search and call tools as needed." })] })),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
export const WithMessages = {
|
|
53
|
+
args: {
|
|
54
|
+
messages: sampleMessages,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
export const Streaming = {
|
|
58
|
+
args: {
|
|
59
|
+
messages: [
|
|
60
|
+
...sampleMessages,
|
|
61
|
+
{
|
|
62
|
+
id: 'm-4',
|
|
63
|
+
role: 'assistant',
|
|
64
|
+
content: "Day 1 — Asakusa & Akihabara. Morning: visit Sensō-ji",
|
|
65
|
+
createdAt: NOW,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
isStreaming: true,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
export const Disabled = {
|
|
72
|
+
args: {
|
|
73
|
+
messages: sampleMessages,
|
|
74
|
+
disabled: true,
|
|
75
|
+
inputPlaceholder: 'Loading conversation…',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
export const WithToolCalls = {
|
|
79
|
+
args: {
|
|
80
|
+
messages: [
|
|
81
|
+
sampleMessages[0],
|
|
82
|
+
{
|
|
83
|
+
id: 'm-tc',
|
|
84
|
+
role: 'assistant',
|
|
85
|
+
content: 'Looking up current restaurant options in Tokyo for you.',
|
|
86
|
+
createdAt: NOW,
|
|
87
|
+
toolCalls: [
|
|
88
|
+
{
|
|
89
|
+
id: 'call-1',
|
|
90
|
+
name: 'web_search',
|
|
91
|
+
arguments: { query: 'best vegetarian restaurants Shibuya 2026' },
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
toolResult: {
|
|
95
|
+
success: true,
|
|
96
|
+
message: 'Found 2 results.',
|
|
97
|
+
data: {
|
|
98
|
+
hits: [
|
|
99
|
+
{ title: 'T\u2019s Tantan (Shibuya)', url: 'https://example.com' },
|
|
100
|
+
{ title: 'AIN SOPH. Soar', url: 'https://example.com' },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Demonstrates the full controlled pattern: parent owns the message list,
|
|
110
|
+
* appends a new turn on submit, and toggles `isStreaming` while the assistant
|
|
111
|
+
* is "responding". Useful for sandbox-testing custom renderers.
|
|
112
|
+
*/
|
|
113
|
+
export const Interactive = {
|
|
114
|
+
args: {
|
|
115
|
+
messages: sampleMessages,
|
|
116
|
+
},
|
|
117
|
+
render: (args) => {
|
|
118
|
+
const InteractiveChat = () => {
|
|
119
|
+
const [messages, setMessages] = useState(args.messages);
|
|
120
|
+
const [streaming, setStreaming] = useState(false);
|
|
121
|
+
return (_jsx(ChatPanel, { ...args, messages: messages, isStreaming: streaming, onSubmit: async ({ content }) => {
|
|
122
|
+
setMessages((prev) => [
|
|
123
|
+
...prev,
|
|
124
|
+
{ id: `u-${prev.length}`, role: 'user', content, createdAt: new Date().toISOString() },
|
|
125
|
+
]);
|
|
126
|
+
setStreaming(true);
|
|
127
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
128
|
+
setMessages((prev) => [
|
|
129
|
+
...prev,
|
|
130
|
+
{
|
|
131
|
+
id: `a-${prev.length}`,
|
|
132
|
+
role: 'assistant',
|
|
133
|
+
content: `Echo: ${content}`,
|
|
134
|
+
createdAt: new Date().toISOString(),
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
setStreaming(false);
|
|
138
|
+
} }));
|
|
139
|
+
};
|
|
140
|
+
return _jsx(InteractiveChat, {});
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=ChatPanel.stories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatPanel.stories.js","sourceRoot":"","sources":["../../src/components/ChatPanel.stories.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MessageList` — render a list of `ChatMessage`s with auto-scroll-to-bottom
|
|
3
|
+
* behaviour. The component is intentionally **headless**: it knows the list
|
|
4
|
+
* shape but lets the caller render every individual bubble.
|
|
5
|
+
*
|
|
6
|
+
* Why a render prop instead of a fixed bubble component? Because every
|
|
7
|
+
* consumer wants to layer additional UI (markdown, search-source pills,
|
|
8
|
+
* mention chips, action renderers) onto the assistant message and we don't
|
|
9
|
+
* want to ship every pluggable variant from `deeppath/apps/web` here.
|
|
10
|
+
*/
|
|
11
|
+
import type { ChatMessage } from '@steerable/agent-protocol';
|
|
12
|
+
export interface MessageRendererProps {
|
|
13
|
+
message: ChatMessage;
|
|
14
|
+
index: number;
|
|
15
|
+
isLastAssistant: boolean;
|
|
16
|
+
isStreaming: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface MessageListProps {
|
|
19
|
+
messages: ChatMessage[];
|
|
20
|
+
isStreaming?: boolean;
|
|
21
|
+
className?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Bubble renderer. If omitted, `MessageList` falls back to the built-in
|
|
24
|
+
* `DefaultMessageBubble` which just renders role + content.
|
|
25
|
+
*/
|
|
26
|
+
renderMessage?: (props: MessageRendererProps) => React.ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* If true (default), scrolls the container to the bottom every time the
|
|
29
|
+
* message list grows or the last assistant message gets new content.
|
|
30
|
+
*/
|
|
31
|
+
autoScroll?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Slot rendered above the first message — typically a "start of conversation"
|
|
34
|
+
* marker or a system prompt summary.
|
|
35
|
+
*/
|
|
36
|
+
header?: React.ReactNode;
|
|
37
|
+
/**
|
|
38
|
+
* Slot rendered when `messages` is empty. Production callers usually drop
|
|
39
|
+
* an `<EmptyChat />` here.
|
|
40
|
+
*/
|
|
41
|
+
emptyState?: React.ReactNode;
|
|
42
|
+
}
|
|
43
|
+
export declare function MessageList(props: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
44
|
+
//# sourceMappingURL=MessageList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAG7D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,KAAK,CAAC,SAAS,CAAC;IACjE;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC9B;AAgCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CAiElD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `MessageList` — render a list of `ChatMessage`s with auto-scroll-to-bottom
|
|
4
|
+
* behaviour. The component is intentionally **headless**: it knows the list
|
|
5
|
+
* shape but lets the caller render every individual bubble.
|
|
6
|
+
*
|
|
7
|
+
* Why a render prop instead of a fixed bubble component? Because every
|
|
8
|
+
* consumer wants to layer additional UI (markdown, search-source pills,
|
|
9
|
+
* mention chips, action renderers) onto the assistant message and we don't
|
|
10
|
+
* want to ship every pluggable variant from `deeppath/apps/web` here.
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
13
|
+
import { cn } from './cn.js';
|
|
14
|
+
function DefaultMessageBubble({ message, isLastAssistant, isStreaming }) {
|
|
15
|
+
const isAssistant = message.role === 'assistant';
|
|
16
|
+
return (_jsx("div", { "data-role": message.role, className: cn('flex w-full', isAssistant ? 'justify-start' : 'justify-end'), children: _jsxs("div", { className: cn('max-w-[80%] rounded-agent-md border px-3 py-2 text-sm leading-relaxed', isAssistant
|
|
17
|
+
? 'border-agent-border bg-agent-muted text-agent-foreground'
|
|
18
|
+
: 'border-transparent bg-agent-accent text-agent-accent-foreground'), children: [_jsx("div", { className: "whitespace-pre-wrap", children: message.content || (isLastAssistant && isStreaming ? '…' : '') }), isLastAssistant && isStreaming ? (_jsx("span", { "aria-hidden": true, className: "ml-0.5 inline-block h-3 w-1.5 align-baseline bg-agent-foreground animate-agent-cursor-blink" })) : null] }) }));
|
|
19
|
+
}
|
|
20
|
+
export function MessageList(props) {
|
|
21
|
+
const { messages, isStreaming = false, className, renderMessage, autoScroll = true, header, emptyState, } = props;
|
|
22
|
+
const containerRef = useRef(null);
|
|
23
|
+
const lastAssistantIndex = useMemo(() => {
|
|
24
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
25
|
+
if (messages[i].role === 'assistant')
|
|
26
|
+
return i;
|
|
27
|
+
}
|
|
28
|
+
return -1;
|
|
29
|
+
}, [messages]);
|
|
30
|
+
// Scroll to bottom on growth or when the streaming assistant gets longer.
|
|
31
|
+
const lastAssistantContent = lastAssistantIndex >= 0 ? messages[lastAssistantIndex].content : '';
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!autoScroll)
|
|
34
|
+
return;
|
|
35
|
+
const el = containerRef.current;
|
|
36
|
+
if (!el)
|
|
37
|
+
return;
|
|
38
|
+
el.scrollTop = el.scrollHeight;
|
|
39
|
+
}, [autoScroll, messages.length, lastAssistantContent]);
|
|
40
|
+
const Renderer = renderMessage ?? DefaultMessageBubble;
|
|
41
|
+
return (_jsxs("div", { ref: containerRef, className: cn('flex h-full w-full flex-col gap-3 overflow-y-auto bg-agent-canvas px-4 py-3', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-agent-accent', className), role: "log", "aria-live": "polite", "aria-label": "Conversation", tabIndex: 0, children: [header, messages.length === 0 ? (emptyState ?? (_jsx("div", { className: "m-auto text-sm text-agent-muted-foreground", children: "No messages yet." }))) : (messages.map((message, index) => (_jsx(Renderer, { message: message, index: index, isLastAssistant: index === lastAssistantIndex, isStreaming: isStreaming }, message.id ?? index))))] }));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=MessageList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageList.js","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { MessageList } from './MessageList.js';
|
|
3
|
+
declare const meta: Meta<typeof MessageList>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof MessageList>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const Empty: Story;
|
|
8
|
+
export declare const EmptyWithCustomState: Story;
|
|
9
|
+
export declare const Streaming: Story;
|
|
10
|
+
export declare const StreamingEmptyAssistant: Story;
|
|
11
|
+
export declare const LongConversation: Story;
|
|
12
|
+
//# sourceMappingURL=MessageList.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageList.stories.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,WAAW,CAmBlC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,WAAW,CAAC,CAAC;AAuB1C,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAInB,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,KAUlC,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KAavB,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,KAQrC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAa9B,CAAC"}
|