@btst/stack 2.3.0 → 2.5.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/dist/packages/stack/src/client/components/compose.cjs +1 -2
- package/dist/packages/stack/src/client/components/compose.mjs +1 -2
- package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.cjs +71 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.mjs +68 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +87 -54
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +87 -54
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +2 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +2 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +89 -22
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +90 -23
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +110 -33
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +112 -35
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.cjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.mjs +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/client/plugin.cjs +14 -21
- package/dist/packages/stack/src/plugins/ai-chat/client/plugin.mjs +15 -22
- package/dist/packages/stack/src/plugins/ai-chat/schemas.cjs +17 -1
- package/dist/packages/stack/src/plugins/ai-chat/schemas.mjs +17 -1
- package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +28 -45
- package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +22 -39
- package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.cjs +15 -2
- package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.mjs +16 -3
- package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.cjs +24 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.mjs +24 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/fill-blog-form-handler.cjs +26 -0
- package/dist/packages/stack/src/plugins/blog/client/components/pages/fill-blog-form-handler.mjs +24 -0
- package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.cjs +30 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.mjs +30 -1
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +18 -0
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +18 -0
- package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +23 -27
- package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +24 -28
- package/dist/packages/stack/src/plugins/cms/api/mutations.cjs +48 -0
- package/dist/packages/stack/src/plugins/cms/api/mutations.mjs +46 -0
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +21 -18
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +21 -18
- package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +11 -15
- package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +12 -16
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +58 -62
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +58 -62
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +12 -12
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +13 -13
- package/dist/packages/stack/src/plugins/kanban/api/mutations.cjs +91 -0
- package/dist/packages/stack/src/plugins/kanban/api/mutations.mjs +87 -0
- package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +92 -118
- package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +89 -115
- package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.cjs +7 -3
- package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.mjs +7 -3
- package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +22 -29
- package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +23 -30
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.cjs +89 -0
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.mjs +89 -0
- package/dist/packages/stack/src/plugins/ui-builder/client/plugin.cjs +8 -8
- package/dist/packages/stack/src/plugins/ui-builder/client/plugin.mjs +9 -9
- package/dist/packages/stack/src/plugins/utils.cjs +42 -0
- package/dist/packages/stack/src/plugins/utils.mjs +41 -1
- package/dist/plugins/ai-chat/api/index.d.cts +1 -1
- package/dist/plugins/ai-chat/api/index.d.mts +1 -1
- package/dist/plugins/ai-chat/api/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/context/page-ai-context.cjs +92 -0
- package/dist/plugins/ai-chat/client/context/page-ai-context.d.cts +84 -0
- package/dist/plugins/ai-chat/client/context/page-ai-context.d.mts +84 -0
- package/dist/plugins/ai-chat/client/context/page-ai-context.d.ts +84 -0
- package/dist/plugins/ai-chat/client/context/page-ai-context.mjs +88 -0
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/index.d.cts +10 -10
- package/dist/plugins/ai-chat/client/index.d.mts +10 -10
- package/dist/plugins/ai-chat/client/index.d.ts +10 -10
- package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
- package/dist/plugins/blog/api/index.d.cts +2 -2
- package/dist/plugins/blog/api/index.d.mts +2 -2
- package/dist/plugins/blog/api/index.d.ts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +13 -13
- package/dist/plugins/blog/client/index.d.mts +13 -13
- package/dist/plugins/blog/client/index.d.ts +13 -13
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/client/index.cjs +1 -0
- package/dist/plugins/client/index.d.cts +8 -1
- package/dist/plugins/client/index.d.mts +8 -1
- package/dist/plugins/client/index.d.ts +8 -1
- package/dist/plugins/client/index.mjs +1 -1
- package/dist/plugins/cms/api/index.cjs +2 -0
- package/dist/plugins/cms/api/index.d.cts +2 -2
- package/dist/plugins/cms/api/index.d.mts +2 -2
- package/dist/plugins/cms/api/index.d.ts +2 -2
- package/dist/plugins/cms/api/index.mjs +1 -0
- package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
- package/dist/plugins/cms/client/index.d.cts +6 -6
- package/dist/plugins/cms/client/index.d.mts +6 -6
- package/dist/plugins/cms/client/index.d.ts +6 -6
- package/dist/plugins/cms/query-keys.d.cts +2 -2
- package/dist/plugins/cms/query-keys.d.mts +2 -2
- package/dist/plugins/cms/query-keys.d.ts +2 -2
- package/dist/plugins/form-builder/api/index.d.cts +2 -2
- package/dist/plugins/form-builder/api/index.d.mts +2 -2
- package/dist/plugins/form-builder/api/index.d.ts +2 -2
- package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/index.d.cts +6 -6
- package/dist/plugins/form-builder/client/index.d.mts +6 -6
- package/dist/plugins/form-builder/client/index.d.ts +6 -6
- package/dist/plugins/form-builder/query-keys.d.cts +2 -2
- package/dist/plugins/form-builder/query-keys.d.mts +2 -2
- package/dist/plugins/form-builder/query-keys.d.ts +2 -2
- package/dist/plugins/kanban/api/index.cjs +4 -0
- package/dist/plugins/kanban/api/index.d.cts +1 -1
- package/dist/plugins/kanban/api/index.d.mts +1 -1
- package/dist/plugins/kanban/api/index.d.ts +1 -1
- package/dist/plugins/kanban/api/index.mjs +1 -0
- package/dist/plugins/kanban/client/index.d.cts +12 -12
- package/dist/plugins/kanban/client/index.d.mts +12 -12
- package/dist/plugins/kanban/client/index.d.ts +12 -12
- package/dist/plugins/kanban/query-keys.d.cts +1 -1
- package/dist/plugins/kanban/query-keys.d.mts +1 -1
- package/dist/plugins/kanban/query-keys.d.ts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/index.d.cts +3 -3
- package/dist/plugins/ui-builder/client/index.d.mts +3 -3
- package/dist/plugins/ui-builder/client/index.d.ts +3 -3
- package/dist/plugins/ui-builder/index.d.cts +2 -2
- package/dist/plugins/ui-builder/index.d.mts +2 -2
- package/dist/plugins/ui-builder/index.d.ts +2 -2
- package/dist/shared/{stack.C-WUPMT6.d.cts → stack.B2xZTSiO.d.cts} +4 -4
- package/dist/shared/{stack.B1EeBt1b.d.ts → stack.B58oHdqm.d.mts} +33 -3
- package/dist/shared/{stack.CVDTkMoO.d.mts → stack.B8QD11QU.d.cts} +7 -7
- package/dist/shared/{stack.CVDTkMoO.d.cts → stack.B8QD11QU.d.mts} +7 -7
- package/dist/shared/{stack.CVDTkMoO.d.ts → stack.B8QD11QU.d.ts} +7 -7
- package/dist/shared/{stack.CIP6QS9l.d.ts → stack.BDVEpue1.d.ts} +1 -1
- package/dist/shared/{stack.C5dtIncc.d.mts → stack.BTvbxZvw.d.cts} +1 -1
- package/dist/shared/{stack.DaOcgmrM.d.ts → stack.BV9hnvu4.d.cts} +31 -7
- package/dist/shared/{stack.DaOcgmrM.d.cts → stack.BV9hnvu4.d.mts} +31 -7
- package/dist/shared/{stack.DaOcgmrM.d.mts → stack.BV9hnvu4.d.ts} +31 -7
- package/dist/shared/{stack.DdI5W6MB.d.mts → stack.BozPgbrZ.d.cts} +19 -19
- package/dist/shared/{stack.DdI5W6MB.d.ts → stack.BozPgbrZ.d.mts} +19 -19
- package/dist/shared/{stack.DdI5W6MB.d.cts → stack.BozPgbrZ.d.ts} +19 -19
- package/dist/shared/{stack.CP68pFEH.d.mts → stack.C9Mg2Q46.d.cts} +33 -3
- package/dist/shared/{stack.BeSm90va.d.ts → stack.CTDVxbrA.d.ts} +72 -14
- package/dist/shared/{stack.C-Ptrz8s.d.ts → stack.Cj_zKww4.d.ts} +4 -4
- package/dist/shared/{stack.TIBF2AOx.d.ts → stack.CxaFNQCV.d.mts} +89 -34
- package/dist/shared/{stack.CMh_EdxW.d.cts → stack.D-b5zbPm.d.cts} +72 -14
- package/dist/shared/{stack.Dw0Ly2TM.d.cts → stack.DTtmJPQO.d.mts} +1 -1
- package/dist/shared/{stack.BKfolAyK.d.ts → stack.DXnclTG7.d.ts} +11 -11
- package/dist/shared/{stack.snB1EDP7.d.cts → stack.DaZM10cp.d.cts} +11 -11
- package/dist/shared/{stack.Dg09R0oB.d.mts → stack.FVWf2JhZ.d.mts} +72 -14
- package/dist/shared/{stack.BIXEI6v_.d.mts → stack.cfCkioTe.d.mts} +11 -11
- package/dist/shared/{stack.6fUOjLs9.d.mts → stack.dH7u-TJH.d.mts} +4 -4
- package/dist/shared/{stack.BpolpQpf.d.cts → stack.j75TpKh2.d.ts} +89 -34
- package/dist/shared/{stack.rTy7-wQU.d.mts → stack.n1_i1p2B.d.cts} +89 -34
- package/dist/shared/{stack.IdtKDRka.d.cts → stack.sO33ZDhK.d.ts} +33 -3
- package/package.json +14 -1
- package/src/client/components/compose.tsx +7 -4
- package/src/plugins/ai-chat/api/page-tools.ts +111 -0
- package/src/plugins/ai-chat/api/plugin.ts +228 -72
- package/src/plugins/ai-chat/client/components/chat-input.tsx +2 -2
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +154 -58
- package/src/plugins/ai-chat/client/components/chat-layout.tsx +166 -32
- package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +1 -1
- package/src/plugins/ai-chat/client/context/page-ai-context.tsx +240 -0
- package/src/plugins/ai-chat/client/plugin.tsx +23 -31
- package/src/plugins/ai-chat/schemas.ts +16 -0
- package/src/plugins/blog/api/plugin.ts +31 -47
- package/src/plugins/blog/client/components/forms/post-forms.tsx +29 -2
- package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +28 -0
- package/src/plugins/blog/client/components/pages/fill-blog-form-handler.ts +38 -0
- package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +33 -1
- package/src/plugins/blog/client/components/pages/post-page.internal.tsx +20 -0
- package/src/plugins/blog/client/plugin.tsx +36 -39
- package/src/plugins/client/index.ts +5 -1
- package/src/plugins/cms/api/index.ts +4 -0
- package/src/plugins/cms/api/mutations.ts +84 -0
- package/src/plugins/cms/api/plugin.ts +23 -17
- package/src/plugins/cms/client/plugin.tsx +18 -21
- package/src/plugins/cms/types.ts +7 -7
- package/src/plugins/form-builder/api/plugin.ts +64 -64
- package/src/plugins/form-builder/client/plugin.tsx +19 -18
- package/src/plugins/form-builder/types.ts +19 -24
- package/src/plugins/kanban/api/index.ts +6 -0
- package/src/plugins/kanban/api/mutations.ts +169 -0
- package/src/plugins/kanban/api/plugin.ts +123 -136
- package/src/plugins/kanban/client/hooks/kanban-hooks.tsx +4 -0
- package/src/plugins/kanban/client/plugin.tsx +35 -41
- package/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.tsx +132 -0
- package/src/plugins/ui-builder/client/plugin.tsx +11 -10
- package/src/plugins/ui-builder/types.ts +4 -4
- package/src/plugins/utils.ts +92 -1
- package/dist/shared/{stack.CBON0dWL.d.mts → stack.BQmuNl5p.d.cts} +2 -2
- package/dist/shared/{stack.CBON0dWL.d.ts → stack.BQmuNl5p.d.mts} +2 -2
- package/dist/shared/{stack.CBON0dWL.d.cts → stack.BQmuNl5p.d.ts} +2 -2
|
@@ -16,6 +16,7 @@ const index = require('../localization/index.cjs');
|
|
|
16
16
|
const client = require('@btst/stack/plugins/client');
|
|
17
17
|
const plugins_aiChat_queryKeys = require('../../../../../../../plugins/ai-chat/query-keys.cjs');
|
|
18
18
|
const chatHooks = require('../hooks/chat-hooks.cjs');
|
|
19
|
+
const plugins_aiChat_client_context_pageAiContext = require('../../../../../../../plugins/ai-chat/client/context/page-ai-context.cjs');
|
|
19
20
|
|
|
20
21
|
function ChatInterface({
|
|
21
22
|
apiPath = "/api/chat",
|
|
@@ -40,6 +41,7 @@ function ChatInterface({
|
|
|
40
41
|
);
|
|
41
42
|
const basePath = context.useBasePath();
|
|
42
43
|
const isPublicMode = mode === "public";
|
|
44
|
+
const pageAIContext = plugins_aiChat_client_context_pageAiContext.usePageAIContext();
|
|
43
45
|
const localization = { ...index.AI_CHAT_LOCALIZATION, ...customLocalization };
|
|
44
46
|
const queryClient = reactQuery.useQueryClient();
|
|
45
47
|
const conversationsListQueryKey = React.useMemo(() => {
|
|
@@ -83,13 +85,25 @@ function ChatInterface({
|
|
|
83
85
|
!initialMessages || initialMessages.length === 0
|
|
84
86
|
)
|
|
85
87
|
);
|
|
88
|
+
const pageAIContextRef = React.useRef(pageAIContext);
|
|
89
|
+
React.useEffect(() => {
|
|
90
|
+
pageAIContextRef.current = pageAIContext;
|
|
91
|
+
}, [pageAIContext]);
|
|
86
92
|
const transport = React.useMemo(
|
|
87
93
|
() => new ai.DefaultChatTransport({
|
|
88
94
|
api: apiPath,
|
|
89
95
|
// In public mode, don't send conversationId
|
|
90
96
|
body: isPublicMode ? void 0 : () => ({ conversationId: conversationIdRef.current }),
|
|
91
|
-
// Handle edit operations
|
|
97
|
+
// Handle edit operations and inject page context
|
|
92
98
|
prepareSendMessagesRequest: ({ messages: hookMessages }) => {
|
|
99
|
+
const currentPageContext = pageAIContextRef.current;
|
|
100
|
+
const pageContextBody = currentPageContext?.pageDescription ? {
|
|
101
|
+
pageContext: currentPageContext.pageDescription,
|
|
102
|
+
availableTools: Object.keys(
|
|
103
|
+
currentPageContext.clientTools ?? {}
|
|
104
|
+
),
|
|
105
|
+
routeName: currentPageContext.routeName
|
|
106
|
+
} : {};
|
|
93
107
|
if (editMessagesRef.current !== null) {
|
|
94
108
|
const newUserMessage = hookMessages[hookMessages.length - 1];
|
|
95
109
|
const messagesToSend = [...editMessagesRef.current];
|
|
@@ -100,22 +114,63 @@ function ChatInterface({
|
|
|
100
114
|
return {
|
|
101
115
|
body: {
|
|
102
116
|
messages: messagesToSend,
|
|
103
|
-
conversationId: conversationIdRef.current
|
|
117
|
+
conversationId: conversationIdRef.current,
|
|
118
|
+
...pageContextBody
|
|
104
119
|
}
|
|
105
120
|
};
|
|
106
121
|
}
|
|
107
122
|
return {
|
|
108
123
|
body: {
|
|
109
124
|
messages: hookMessages,
|
|
110
|
-
conversationId: conversationIdRef.current
|
|
125
|
+
conversationId: conversationIdRef.current,
|
|
126
|
+
...pageContextBody
|
|
111
127
|
}
|
|
112
128
|
};
|
|
113
129
|
}
|
|
114
130
|
}),
|
|
115
131
|
[apiPath, isPublicMode]
|
|
116
132
|
);
|
|
117
|
-
const
|
|
133
|
+
const addToolOutputRef = React.useRef(null);
|
|
134
|
+
const {
|
|
135
|
+
messages,
|
|
136
|
+
sendMessage,
|
|
137
|
+
status,
|
|
138
|
+
error,
|
|
139
|
+
setMessages,
|
|
140
|
+
regenerate,
|
|
141
|
+
addToolOutput
|
|
142
|
+
} = react.useChat({
|
|
118
143
|
transport,
|
|
144
|
+
// Automatically resubmit after all client-side tool results are provided
|
|
145
|
+
sendAutomaticallyWhen: ai.lastAssistantMessageIsCompleteWithToolCalls,
|
|
146
|
+
onToolCall: async ({ toolCall }) => {
|
|
147
|
+
const toolName = toolCall.toolName;
|
|
148
|
+
const handler = pageAIContextRef.current?.clientTools?.[toolName];
|
|
149
|
+
if (handler) {
|
|
150
|
+
try {
|
|
151
|
+
const result = await handler(toolCall.input);
|
|
152
|
+
addToolOutputRef.current?.({
|
|
153
|
+
tool: toolName,
|
|
154
|
+
toolCallId: toolCall.toolCallId,
|
|
155
|
+
output: result
|
|
156
|
+
});
|
|
157
|
+
} catch (err) {
|
|
158
|
+
addToolOutputRef.current?.({
|
|
159
|
+
tool: toolName,
|
|
160
|
+
toolCallId: toolCall.toolCallId,
|
|
161
|
+
state: "output-error",
|
|
162
|
+
errorText: err instanceof Error ? err.message : "Tool execution failed"
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
addToolOutputRef.current?.({
|
|
167
|
+
tool: toolName,
|
|
168
|
+
toolCallId: toolCall.toolCallId,
|
|
169
|
+
state: "output-error",
|
|
170
|
+
errorText: `No client-side handler registered for tool "${toolName}". The page context may have changed while the response was streaming.`
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
},
|
|
119
174
|
onError: (err) => {
|
|
120
175
|
console.error("useChat onError:", err);
|
|
121
176
|
if (!id && !hasNavigatedRef.current) {
|
|
@@ -138,19 +193,24 @@ function ChatInterface({
|
|
|
138
193
|
if (newConversation) {
|
|
139
194
|
setCurrentConversationId(newConversation.id);
|
|
140
195
|
conversationIdRef.current = newConversation.id;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
window
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
196
|
+
if (variant === "full") {
|
|
197
|
+
const newUrl = `${basePath}/chat/${newConversation.id}`;
|
|
198
|
+
if (typeof window !== "undefined") {
|
|
199
|
+
window.history.replaceState(
|
|
200
|
+
{ ...window.history.state },
|
|
201
|
+
"",
|
|
202
|
+
newUrl
|
|
203
|
+
);
|
|
204
|
+
}
|
|
148
205
|
}
|
|
149
206
|
}
|
|
150
207
|
}
|
|
151
208
|
}
|
|
152
209
|
}
|
|
153
210
|
});
|
|
211
|
+
React.useEffect(() => {
|
|
212
|
+
addToolOutputRef.current = addToolOutput;
|
|
213
|
+
}, [addToolOutput]);
|
|
154
214
|
React.useEffect(() => {
|
|
155
215
|
if (isEditInProgressRef.current) {
|
|
156
216
|
return;
|
|
@@ -313,17 +373,24 @@ function ChatInterface({
|
|
|
313
373
|
),
|
|
314
374
|
children: [
|
|
315
375
|
messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-[300px]", children: [
|
|
316
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: localization.CHAT_EMPTY_STATE }) }),
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
376
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: localization.CHAT_EMPTY_STATE }) }),
|
|
377
|
+
(() => {
|
|
378
|
+
const pageSuggestions = pageAIContext?.suggestions ?? [];
|
|
379
|
+
const allSuggestions = [
|
|
380
|
+
...pageSuggestions,
|
|
381
|
+
...chatSuggestions ?? []
|
|
382
|
+
];
|
|
383
|
+
return allSuggestions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap justify-center gap-2 pb-4 max-w-md mx-auto", children: allSuggestions.map((suggestion, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
384
|
+
"button",
|
|
385
|
+
{
|
|
386
|
+
type: "button",
|
|
387
|
+
onClick: () => setInput(suggestion),
|
|
388
|
+
className: "px-3 py-2 text-sm rounded-lg border border-border bg-background hover:bg-accent hover:text-accent-foreground transition-colors text-foreground",
|
|
389
|
+
children: suggestion
|
|
390
|
+
},
|
|
391
|
+
index
|
|
392
|
+
)) }) : null;
|
|
393
|
+
})()
|
|
327
394
|
] }) : messages.map((m, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
328
395
|
chatMessage.ChatMessage,
|
|
329
396
|
{
|
|
@@ -7,13 +7,14 @@ import { ChatMessage } from './chat-message.mjs';
|
|
|
7
7
|
import { ChatInput } from './chat-input.mjs';
|
|
8
8
|
import { StackAttribution } from '../../../../../../ui/src/components/stack-attribution.mjs';
|
|
9
9
|
import { ScrollArea } from '../../../../../../ui/src/components/scroll-area.mjs';
|
|
10
|
-
import { DefaultChatTransport } from 'ai';
|
|
10
|
+
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
|
|
11
11
|
import { cn } from '../../../../../../ui/src/lib/utils.mjs';
|
|
12
12
|
import { usePluginOverrides, useBasePath } from '@btst/stack/context';
|
|
13
13
|
import { AI_CHAT_LOCALIZATION } from '../localization/index.mjs';
|
|
14
14
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
15
15
|
import { createAiChatQueryKeys } from '../../../../../../../plugins/ai-chat/query-keys.mjs';
|
|
16
16
|
import { useConversation, useConversations } from '../hooks/chat-hooks.mjs';
|
|
17
|
+
import { usePageAIContext } from '../../../../../../../plugins/ai-chat/client/context/page-ai-context.mjs';
|
|
17
18
|
|
|
18
19
|
function ChatInterface({
|
|
19
20
|
apiPath = "/api/chat",
|
|
@@ -38,6 +39,7 @@ function ChatInterface({
|
|
|
38
39
|
);
|
|
39
40
|
const basePath = useBasePath();
|
|
40
41
|
const isPublicMode = mode === "public";
|
|
42
|
+
const pageAIContext = usePageAIContext();
|
|
41
43
|
const localization = { ...AI_CHAT_LOCALIZATION, ...customLocalization };
|
|
42
44
|
const queryClient = useQueryClient();
|
|
43
45
|
const conversationsListQueryKey = useMemo(() => {
|
|
@@ -81,13 +83,25 @@ function ChatInterface({
|
|
|
81
83
|
!initialMessages || initialMessages.length === 0
|
|
82
84
|
)
|
|
83
85
|
);
|
|
86
|
+
const pageAIContextRef = useRef(pageAIContext);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
pageAIContextRef.current = pageAIContext;
|
|
89
|
+
}, [pageAIContext]);
|
|
84
90
|
const transport = useMemo(
|
|
85
91
|
() => new DefaultChatTransport({
|
|
86
92
|
api: apiPath,
|
|
87
93
|
// In public mode, don't send conversationId
|
|
88
94
|
body: isPublicMode ? void 0 : () => ({ conversationId: conversationIdRef.current }),
|
|
89
|
-
// Handle edit operations
|
|
95
|
+
// Handle edit operations and inject page context
|
|
90
96
|
prepareSendMessagesRequest: ({ messages: hookMessages }) => {
|
|
97
|
+
const currentPageContext = pageAIContextRef.current;
|
|
98
|
+
const pageContextBody = currentPageContext?.pageDescription ? {
|
|
99
|
+
pageContext: currentPageContext.pageDescription,
|
|
100
|
+
availableTools: Object.keys(
|
|
101
|
+
currentPageContext.clientTools ?? {}
|
|
102
|
+
),
|
|
103
|
+
routeName: currentPageContext.routeName
|
|
104
|
+
} : {};
|
|
91
105
|
if (editMessagesRef.current !== null) {
|
|
92
106
|
const newUserMessage = hookMessages[hookMessages.length - 1];
|
|
93
107
|
const messagesToSend = [...editMessagesRef.current];
|
|
@@ -98,22 +112,63 @@ function ChatInterface({
|
|
|
98
112
|
return {
|
|
99
113
|
body: {
|
|
100
114
|
messages: messagesToSend,
|
|
101
|
-
conversationId: conversationIdRef.current
|
|
115
|
+
conversationId: conversationIdRef.current,
|
|
116
|
+
...pageContextBody
|
|
102
117
|
}
|
|
103
118
|
};
|
|
104
119
|
}
|
|
105
120
|
return {
|
|
106
121
|
body: {
|
|
107
122
|
messages: hookMessages,
|
|
108
|
-
conversationId: conversationIdRef.current
|
|
123
|
+
conversationId: conversationIdRef.current,
|
|
124
|
+
...pageContextBody
|
|
109
125
|
}
|
|
110
126
|
};
|
|
111
127
|
}
|
|
112
128
|
}),
|
|
113
129
|
[apiPath, isPublicMode]
|
|
114
130
|
);
|
|
115
|
-
const
|
|
131
|
+
const addToolOutputRef = useRef(null);
|
|
132
|
+
const {
|
|
133
|
+
messages,
|
|
134
|
+
sendMessage,
|
|
135
|
+
status,
|
|
136
|
+
error,
|
|
137
|
+
setMessages,
|
|
138
|
+
regenerate,
|
|
139
|
+
addToolOutput
|
|
140
|
+
} = useChat({
|
|
116
141
|
transport,
|
|
142
|
+
// Automatically resubmit after all client-side tool results are provided
|
|
143
|
+
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
|
|
144
|
+
onToolCall: async ({ toolCall }) => {
|
|
145
|
+
const toolName = toolCall.toolName;
|
|
146
|
+
const handler = pageAIContextRef.current?.clientTools?.[toolName];
|
|
147
|
+
if (handler) {
|
|
148
|
+
try {
|
|
149
|
+
const result = await handler(toolCall.input);
|
|
150
|
+
addToolOutputRef.current?.({
|
|
151
|
+
tool: toolName,
|
|
152
|
+
toolCallId: toolCall.toolCallId,
|
|
153
|
+
output: result
|
|
154
|
+
});
|
|
155
|
+
} catch (err) {
|
|
156
|
+
addToolOutputRef.current?.({
|
|
157
|
+
tool: toolName,
|
|
158
|
+
toolCallId: toolCall.toolCallId,
|
|
159
|
+
state: "output-error",
|
|
160
|
+
errorText: err instanceof Error ? err.message : "Tool execution failed"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
addToolOutputRef.current?.({
|
|
165
|
+
tool: toolName,
|
|
166
|
+
toolCallId: toolCall.toolCallId,
|
|
167
|
+
state: "output-error",
|
|
168
|
+
errorText: `No client-side handler registered for tool "${toolName}". The page context may have changed while the response was streaming.`
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
},
|
|
117
172
|
onError: (err) => {
|
|
118
173
|
console.error("useChat onError:", err);
|
|
119
174
|
if (!id && !hasNavigatedRef.current) {
|
|
@@ -136,19 +191,24 @@ function ChatInterface({
|
|
|
136
191
|
if (newConversation) {
|
|
137
192
|
setCurrentConversationId(newConversation.id);
|
|
138
193
|
conversationIdRef.current = newConversation.id;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
window
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
194
|
+
if (variant === "full") {
|
|
195
|
+
const newUrl = `${basePath}/chat/${newConversation.id}`;
|
|
196
|
+
if (typeof window !== "undefined") {
|
|
197
|
+
window.history.replaceState(
|
|
198
|
+
{ ...window.history.state },
|
|
199
|
+
"",
|
|
200
|
+
newUrl
|
|
201
|
+
);
|
|
202
|
+
}
|
|
146
203
|
}
|
|
147
204
|
}
|
|
148
205
|
}
|
|
149
206
|
}
|
|
150
207
|
}
|
|
151
208
|
});
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
addToolOutputRef.current = addToolOutput;
|
|
211
|
+
}, [addToolOutput]);
|
|
152
212
|
useEffect(() => {
|
|
153
213
|
if (isEditInProgressRef.current) {
|
|
154
214
|
return;
|
|
@@ -311,17 +371,24 @@ function ChatInterface({
|
|
|
311
371
|
),
|
|
312
372
|
children: [
|
|
313
373
|
messages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-[300px]", children: [
|
|
314
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground", children: /* @__PURE__ */ jsx("p", { children: localization.CHAT_EMPTY_STATE }) }),
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
374
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground mb-4", children: /* @__PURE__ */ jsx("p", { children: localization.CHAT_EMPTY_STATE }) }),
|
|
375
|
+
(() => {
|
|
376
|
+
const pageSuggestions = pageAIContext?.suggestions ?? [];
|
|
377
|
+
const allSuggestions = [
|
|
378
|
+
...pageSuggestions,
|
|
379
|
+
...chatSuggestions ?? []
|
|
380
|
+
];
|
|
381
|
+
return allSuggestions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-center gap-2 pb-4 max-w-md mx-auto", children: allSuggestions.map((suggestion, index) => /* @__PURE__ */ jsx(
|
|
382
|
+
"button",
|
|
383
|
+
{
|
|
384
|
+
type: "button",
|
|
385
|
+
onClick: () => setInput(suggestion),
|
|
386
|
+
className: "px-3 py-2 text-sm rounded-lg border border-border bg-background hover:bg-accent hover:text-accent-foreground transition-colors text-foreground",
|
|
387
|
+
children: suggestion
|
|
388
|
+
},
|
|
389
|
+
index
|
|
390
|
+
)) }) : null;
|
|
391
|
+
})()
|
|
325
392
|
] }) : messages.map((m, index) => /* @__PURE__ */ jsx(
|
|
326
393
|
ChatMessage,
|
|
327
394
|
{
|
|
@@ -4,26 +4,36 @@
|
|
|
4
4
|
const jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
const React = require('react');
|
|
6
6
|
const button = require('../../../../../../ui/src/components/button.cjs');
|
|
7
|
+
const badge = require('../../../../../../ui/src/components/badge.cjs');
|
|
7
8
|
const sheet = require('../../../../../../ui/src/components/sheet.cjs');
|
|
8
9
|
const LucideIcons = require('lucide-react');
|
|
9
10
|
const utils = require('../../../../../../ui/src/lib/utils.cjs');
|
|
10
11
|
const chatSidebar = require('./chat-sidebar.cjs');
|
|
11
12
|
const chatInterface = require('./chat-interface.cjs');
|
|
13
|
+
const plugins_aiChat_client_context_pageAiContext = require('../../../../../../../plugins/ai-chat/client/context/page-ai-context.cjs');
|
|
12
14
|
|
|
13
|
-
function ChatLayout({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
15
|
+
function ChatLayout(props) {
|
|
16
|
+
const {
|
|
17
|
+
apiBaseURL,
|
|
18
|
+
apiBasePath,
|
|
19
|
+
conversationId,
|
|
20
|
+
layout = "full",
|
|
21
|
+
className,
|
|
22
|
+
showSidebar = true,
|
|
23
|
+
initialMessages,
|
|
24
|
+
onMessagesChange
|
|
25
|
+
} = props;
|
|
26
|
+
const widgetHeight = props.layout === "widget" ? props.widgetHeight ?? "600px" : "600px";
|
|
27
|
+
const widgetWidth = props.layout === "widget" ? props.widgetWidth ?? "380px" : "380px";
|
|
28
|
+
const defaultOpen = props.layout === "widget" ? props.defaultOpen ?? false : false;
|
|
29
|
+
const showTrigger = props.layout === "widget" ? props.showTrigger ?? true : true;
|
|
24
30
|
const [sidebarOpen, setSidebarOpen] = React.useState(true);
|
|
25
31
|
const [mobileSidebarOpen, setMobileSidebarOpen] = React.useState(false);
|
|
26
32
|
const [chatResetKey, setChatResetKey] = React.useState(0);
|
|
33
|
+
const [widgetOpen, setWidgetOpen] = React.useState(defaultOpen);
|
|
34
|
+
const [widgetResetKey, setWidgetResetKey] = React.useState(0);
|
|
35
|
+
const [widgetEverOpened, setWidgetEverOpened] = React.useState(defaultOpen);
|
|
36
|
+
const pageAIContext = plugins_aiChat_client_context_pageAiContext.usePageAIContext();
|
|
27
37
|
const apiPath = `${apiBaseURL}${apiBasePath}/chat`;
|
|
28
38
|
const handleNewChat = React.useCallback(() => {
|
|
29
39
|
if (!conversationId) {
|
|
@@ -31,26 +41,81 @@ function ChatLayout({
|
|
|
31
41
|
}
|
|
32
42
|
}, [conversationId]);
|
|
33
43
|
if (layout === "widget") {
|
|
34
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex flex-col items-end gap-3", className), children: [
|
|
45
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
46
|
+
"div",
|
|
47
|
+
{
|
|
48
|
+
className: utils.cn(
|
|
49
|
+
"flex flex-col border rounded-xl overflow-hidden bg-background shadow-xl",
|
|
50
|
+
widgetOpen ? "flex" : "hidden"
|
|
51
|
+
),
|
|
52
|
+
style: { height: widgetHeight, width: widgetWidth },
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 px-3 py-1.5 border-b bg-muted/40", children: [
|
|
55
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Sparkles, { className: "h-3 w-3 text-muted-foreground" }),
|
|
56
|
+
pageAIContext ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
57
|
+
badge.Badge,
|
|
58
|
+
{
|
|
59
|
+
variant: "secondary",
|
|
60
|
+
className: "text-xs",
|
|
61
|
+
"data-testid": "page-context-badge",
|
|
62
|
+
children: pageAIContext.routeName
|
|
63
|
+
}
|
|
64
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground font-medium", children: "AI Chat" }),
|
|
65
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
|
|
66
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
67
|
+
button.Button,
|
|
68
|
+
{
|
|
69
|
+
variant: "ghost",
|
|
70
|
+
size: "icon",
|
|
71
|
+
className: "h-5 w-5",
|
|
72
|
+
onClick: () => setWidgetResetKey((prev) => prev + 1),
|
|
73
|
+
"aria-label": "Clear chat",
|
|
74
|
+
title: "Clear chat",
|
|
75
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Trash2, { className: "h-3.5 w-3.5" })
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
79
|
+
button.Button,
|
|
80
|
+
{
|
|
81
|
+
variant: "ghost",
|
|
82
|
+
size: "icon",
|
|
83
|
+
className: "h-5 w-5",
|
|
84
|
+
onClick: () => setWidgetOpen(false),
|
|
85
|
+
"aria-label": "Close chat",
|
|
86
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "h-3.5 w-3.5" })
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
] }),
|
|
90
|
+
widgetEverOpened && /* @__PURE__ */ jsxRuntime.jsx(
|
|
91
|
+
chatInterface.ChatInterface,
|
|
92
|
+
{
|
|
93
|
+
apiPath,
|
|
94
|
+
id: conversationId,
|
|
95
|
+
variant: "widget",
|
|
96
|
+
initialMessages,
|
|
97
|
+
onMessagesChange
|
|
98
|
+
},
|
|
99
|
+
`widget-${conversationId ?? "new"}-${widgetResetKey}`
|
|
100
|
+
)
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
),
|
|
104
|
+
showTrigger && /* @__PURE__ */ jsxRuntime.jsx(
|
|
105
|
+
button.Button,
|
|
106
|
+
{
|
|
107
|
+
size: "icon",
|
|
108
|
+
className: "h-12 w-12 rounded-full shadow-lg",
|
|
109
|
+
onClick: () => {
|
|
110
|
+
setWidgetOpen((prev) => !prev);
|
|
111
|
+
setWidgetEverOpened(true);
|
|
112
|
+
},
|
|
113
|
+
"aria-label": widgetOpen ? "Close chat" : "Open chat",
|
|
114
|
+
"data-testid": "widget-trigger",
|
|
115
|
+
children: widgetOpen ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Sparkles, { className: "h-5 w-5" })
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
] });
|
|
54
119
|
}
|
|
55
120
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
56
121
|
"div",
|
|
@@ -79,7 +144,7 @@ function ChatLayout({
|
|
|
79
144
|
}
|
|
80
145
|
),
|
|
81
146
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [
|
|
82
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 p-2 border-b bg-background/95 backdrop-blur supports-
|
|
147
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 p-2 border-b bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60", children: [
|
|
83
148
|
showSidebar && /* @__PURE__ */ jsxRuntime.jsxs(sheet.Sheet, { open: mobileSidebarOpen, onOpenChange: setMobileSidebarOpen, children: [
|
|
84
149
|
/* @__PURE__ */ jsxRuntime.jsx(sheet.SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
85
150
|
button.Button,
|
|
@@ -113,7 +178,19 @@ function ChatLayout({
|
|
|
113
178
|
children: sidebarOpen ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.PanelLeftClose, { className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.PanelLeft, { className: "h-5 w-5" })
|
|
114
179
|
}
|
|
115
180
|
),
|
|
116
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" })
|
|
181
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
|
|
182
|
+
pageAIContext && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
183
|
+
badge.Badge,
|
|
184
|
+
{
|
|
185
|
+
variant: "secondary",
|
|
186
|
+
className: "text-xs gap-1 mr-2",
|
|
187
|
+
"data-testid": "page-context-badge",
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Sparkles, { className: "h-3 w-3" }),
|
|
190
|
+
pageAIContext.routeName
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
)
|
|
117
194
|
] }),
|
|
118
195
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
119
196
|
chatInterface.ChatInterface,
|