@servicetitan/titan-chatbot-ui-anvil2 8.0.0 β†’ 9.0.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/components/chatbot/__tests-cy__/chatbot-live.test.js +1 -1
  3. package/dist/components/chatbot/__tests-cy__/chatbot-live.test.js.map +1 -1
  4. package/dist/components/chatbot/__tests-cy__/chatbot-streaming.test.d.ts +2 -0
  5. package/dist/components/chatbot/__tests-cy__/chatbot-streaming.test.d.ts.map +1 -0
  6. package/dist/components/chatbot/__tests-cy__/chatbot-streaming.test.js +11 -0
  7. package/dist/components/chatbot/__tests-cy__/chatbot-streaming.test.js.map +1 -0
  8. package/dist/components/chatbot/chatbot.d.ts.map +1 -1
  9. package/dist/components/chatbot/chatbot.js +5 -2
  10. package/dist/components/chatbot/chatbot.js.map +1 -1
  11. package/dist/components/chatbot/messages/chatbot-streaming-progress.d.ts +12 -0
  12. package/dist/components/chatbot/messages/chatbot-streaming-progress.d.ts.map +1 -0
  13. package/dist/components/chatbot/messages/chatbot-streaming-progress.js +116 -0
  14. package/dist/components/chatbot/messages/chatbot-streaming-progress.js.map +1 -0
  15. package/dist/components/chatbot/messages/chatbot-streaming-progress.module.less +4 -0
  16. package/dist/components/chatbot/messages/chatbot-streaming-progress.module.less.d.ts +3 -0
  17. package/package.json +6 -6
  18. package/src/components/chatbot/__tests-cy__/chatbot-live.test.tsx +1 -1
  19. package/src/components/chatbot/__tests-cy__/chatbot-streaming.test.tsx +9 -0
  20. package/src/components/chatbot/chatbot.tsx +7 -1
  21. package/src/components/chatbot/messages/chatbot-streaming-progress.module.less +4 -0
  22. package/src/components/chatbot/messages/chatbot-streaming-progress.module.less.d.ts +3 -0
  23. package/src/components/chatbot/messages/chatbot-streaming-progress.tsx +110 -0
  24. package/tsconfig.tsbuildinfo +1 -1
  25. package/dist/components/chatbot/__tests-cy__/chatbot-help-center.test.d.ts +0 -2
  26. package/dist/components/chatbot/__tests-cy__/chatbot-help-center.test.d.ts.map +0 -1
  27. package/dist/components/chatbot/__tests-cy__/chatbot-help-center.test.js +0 -11
  28. package/dist/components/chatbot/__tests-cy__/chatbot-help-center.test.js.map +0 -1
  29. package/src/components/chatbot/__tests-cy__/chatbot-help-center.test.tsx +0 -9
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # v9.0.0 (Mon Jun 08 2026)
2
+
3
+ #### πŸ’₯ Breaking Change
4
+
5
+ - SPA-8507: Agent Progress Streaming β€” Frontend Streaming UX [#91](https://github.com/servicetitan/titan-chatbot-client/pull/91) ([@AlexYarmolchuk](https://github.com/AlexYarmolchuk))
6
+
7
+ #### Authors: 1
8
+
9
+ - Alexandr Yarmolchuk ([@AlexYarmolchuk](https://github.com/AlexYarmolchuk))
10
+
11
+ ---
12
+
1
13
  # v8.0.0 (Wed Jun 03 2026)
2
14
 
3
15
  #### πŸ’₯ Breaking Change
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { AnvilProvider } from '@servicetitan/anvil2';
3
3
  import { runChatbotLiveSharedTests } from '@servicetitan/cypress-shared';
4
4
  import { Chatbot } from '../chatbot';
5
- describe('[Chatbot]', ()=>{
5
+ describe.skip('[Chatbot]', ()=>{
6
6
  runChatbotLiveSharedTests(Chatbot, (component)=>/*#__PURE__*/ _jsx(AnvilProvider, {
7
7
  children: component
8
8
  }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/chatbot/__tests-cy__/chatbot-live.test.tsx"],"sourcesContent":["import { AnvilProvider } from '@servicetitan/anvil2';\nimport { runChatbotLiveSharedTests } from '@servicetitan/cypress-shared';\nimport { Chatbot } from '../chatbot';\n\ndescribe('[Chatbot]', () => {\n runChatbotLiveSharedTests(Chatbot, component => <AnvilProvider>{component}</AnvilProvider>);\n});\n"],"names":["AnvilProvider","runChatbotLiveSharedTests","Chatbot","describe","component"],"mappings":";AAAA,SAASA,aAAa,QAAQ,uBAAuB;AACrD,SAASC,yBAAyB,QAAQ,+BAA+B;AACzE,SAASC,OAAO,QAAQ,aAAa;AAErCC,SAAS,aAAa;IAClBF,0BAA0BC,SAASE,CAAAA,0BAAa,KAACJ;sBAAeI;;AACpE"}
1
+ {"version":3,"sources":["../../../../src/components/chatbot/__tests-cy__/chatbot-live.test.tsx"],"sourcesContent":["import { AnvilProvider } from '@servicetitan/anvil2';\nimport { runChatbotLiveSharedTests } from '@servicetitan/cypress-shared';\nimport { Chatbot } from '../chatbot';\n\ndescribe.skip('[Chatbot]', () => {\n runChatbotLiveSharedTests(Chatbot, component => <AnvilProvider>{component}</AnvilProvider>);\n});\n"],"names":["AnvilProvider","runChatbotLiveSharedTests","Chatbot","describe","skip","component"],"mappings":";AAAA,SAASA,aAAa,QAAQ,uBAAuB;AACrD,SAASC,yBAAyB,QAAQ,+BAA+B;AACzE,SAASC,OAAO,QAAQ,aAAa;AAErCC,SAASC,IAAI,CAAC,aAAa;IACvBH,0BAA0BC,SAASG,CAAAA,0BAAa,KAACL;sBAAeK;;AACpE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=chatbot-streaming.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatbot-streaming.test.d.ts","sourceRoot":"","sources":["../../../../src/components/chatbot/__tests-cy__/chatbot-streaming.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AnvilProvider } from '@servicetitan/anvil2';
3
+ import { runChatbotStreamingSharedTests } from '@servicetitan/cypress-shared';
4
+ import { Chatbot } from '../chatbot';
5
+ describe('[Chatbot streaming]', ()=>{
6
+ runChatbotStreamingSharedTests(Chatbot, (component)=>/*#__PURE__*/ _jsx(AnvilProvider, {
7
+ children: component
8
+ }));
9
+ });
10
+
11
+ //# sourceMappingURL=chatbot-streaming.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/chatbot/__tests-cy__/chatbot-streaming.test.tsx"],"sourcesContent":["import { AnvilProvider } from '@servicetitan/anvil2';\nimport { runChatbotStreamingSharedTests } from '@servicetitan/cypress-shared';\nimport { Chatbot } from '../chatbot';\n\ndescribe('[Chatbot streaming]', () => {\n runChatbotStreamingSharedTests(Chatbot, component => (\n <AnvilProvider>{component}</AnvilProvider>\n ));\n});\n"],"names":["AnvilProvider","runChatbotStreamingSharedTests","Chatbot","describe","component"],"mappings":";AAAA,SAASA,aAAa,QAAQ,uBAAuB;AACrD,SAASC,8BAA8B,QAAQ,+BAA+B;AAC9E,SAASC,OAAO,QAAQ,aAAa;AAErCC,SAAS,uBAAuB;IAC5BF,+BAA+BC,SAASE,CAAAA,0BACpC,KAACJ;sBAAeI;;AAExB"}
@@ -1 +1 @@
1
- {"version":3,"file":"chatbot.d.ts","sourceRoot":"","sources":["../../../src/components/chatbot/chatbot.tsx"],"names":[],"mappings":"AAGA,OAAO,EAIH,mBAAmB,EACtB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAGH,qBAAqB,EACxB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,EAAE,EAAsB,MAAM,OAAO,CAAC;AAW9D,MAAM,WAAW,aAAa;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,aAAa,CA6ErC,CAAC"}
1
+ {"version":3,"file":"chatbot.d.ts","sourceRoot":"","sources":["../../../src/components/chatbot/chatbot.tsx"],"names":[],"mappings":"AAGA,OAAO,EAIH,mBAAmB,EACtB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAGH,qBAAqB,EAExB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,EAAE,EAAsB,MAAM,OAAO,CAAC;AAY9D,MAAM,WAAW,aAAa;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,aAAa,CAiFrC,CAAC"}
@@ -3,7 +3,7 @@ import { Flex } from '@servicetitan/anvil2';
3
3
  import { useDependencies } from '@servicetitan/react-ioc';
4
4
  import { Chat } from '@servicetitan/titan-chat-ui-anvil2';
5
5
  import { ChatParticipantIcon } from '@servicetitan/titan-chat-ui-common';
6
- import { CHATBOT_UI_BACKEND_STORE_TOKEN, CHATBOT_UI_STORE_TOKEN } from '@servicetitan/titan-chatbot-api';
6
+ import { CHATBOT_UI_BACKEND_STORE_TOKEN, CHATBOT_UI_STORE_TOKEN, isChatbotStreamingEnabled } from '@servicetitan/titan-chatbot-api';
7
7
  import { observer } from 'mobx-react';
8
8
  import { useEffect, useMemo } from 'react';
9
9
  import { ChatbotToChatProviderAdapter } from './chatbot-to-chat-provider-adapter';
@@ -14,6 +14,7 @@ import { ChatbotMessageQuestion } from './messages/chatbot-message-question';
14
14
  import { ChatbotMessageTimeout } from './messages/chatbot-message-timeout';
15
15
  import { ChatbotMessageTyping } from './messages/chatbot-message-typing';
16
16
  import { ChatbotMessageWelcome } from './messages/chatbot-message-welcome';
17
+ import { ChatbotStreamingProgress } from './messages/chatbot-streaming-progress';
17
18
  import { ChatbotMessageTemplateAgent } from './templates/chatbot-message-template-agent';
18
19
  export const Chatbot = observer(({ botIcon, botName, className, customizations, onReady, style })=>{
19
20
  var _customizationsMerged_filters, _customizationsMerged_filters1;
@@ -44,9 +45,11 @@ export const Chatbot = observer(({ botIcon, botName, className, customizations,
44
45
  error: customizations === null || customizations === void 0 ? void 0 : customizations.error,
45
46
  filters: customizations === null || customizations === void 0 ? void 0 : customizations.filters,
46
47
  feedback: customizations === null || customizations === void 0 ? void 0 : customizations.feedback,
48
+ streaming: customizations === null || customizations === void 0 ? void 0 : customizations.streaming,
49
+ timeouts: customizations === null || customizations === void 0 ? void 0 : customizations.timeouts,
47
50
  footerComponent: customizations === null || customizations === void 0 ? void 0 : customizations.footerComponent,
48
51
  messageTyping: (_ref1 = customizations === null || customizations === void 0 ? void 0 : customizations.messageTyping) !== null && _ref1 !== void 0 ? _ref1 : {
49
- component: ChatbotMessageTyping
52
+ component: isChatbotStreamingEnabled(customizations) ? ChatbotStreamingProgress : ChatbotMessageTyping
50
53
  },
51
54
  messages: [
52
55
  ...(_ref2 = customizations === null || customizations === void 0 ? void 0 : customizations.messages) !== null && _ref2 !== void 0 ? _ref2 : [],
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/chatbot/chatbot.tsx"],"sourcesContent":["import { Flex } from '@servicetitan/anvil2';\nimport { useDependencies } from '@servicetitan/react-ioc';\nimport { Chat } from '@servicetitan/titan-chat-ui-anvil2';\nimport {\n ChatMessageModelText,\n ChatMessageModelTimeout,\n ChatMessageModelWelcome,\n ChatParticipantIcon,\n} from '@servicetitan/titan-chat-ui-common';\nimport {\n CHATBOT_UI_BACKEND_STORE_TOKEN,\n CHATBOT_UI_STORE_TOKEN,\n ChatbotCustomizations,\n} from '@servicetitan/titan-chatbot-api';\nimport { observer } from 'mobx-react';\nimport { CSSProperties, FC, useEffect, useMemo } from 'react';\nimport { ChatbotToChatProviderAdapter } from './chatbot-to-chat-provider-adapter';\nimport * as Styles from './chatbot.module.less';\nimport { ChatFilters } from './filters/chatbot-filters';\nimport { ChatbotMessageAnswer } from './messages/chatbot-message-answer';\nimport { ChatbotMessageQuestion } from './messages/chatbot-message-question';\nimport { ChatbotMessageTimeout } from './messages/chatbot-message-timeout';\nimport { ChatbotMessageTyping } from './messages/chatbot-message-typing';\nimport { ChatbotMessageWelcome } from './messages/chatbot-message-welcome';\nimport { ChatbotMessageTemplateAgent } from './templates/chatbot-message-template-agent';\n\nexport interface IChatbotProps {\n className?: string;\n style?: CSSProperties;\n customizations?: ChatbotCustomizations;\n botIcon?: ChatParticipantIcon;\n botName?: string;\n onReady?: () => void;\n}\n\nexport const Chatbot: FC<IChatbotProps> = observer(\n ({ botIcon, botName, className, customizations, onReady, style }) => {\n const [chatUiStore, chatUiBackendStore] = useDependencies(\n CHATBOT_UI_STORE_TOKEN,\n CHATBOT_UI_BACKEND_STORE_TOKEN\n );\n useEffect(() => {\n const init = async () => {\n chatUiBackendStore.subscribe();\n await chatUiStore.run({\n agentName: botName ?? 'Titan',\n agentIcon: botIcon ?? ChatParticipantIcon.Bot,\n });\n onReady?.();\n };\n init().then(() => {});\n return () => chatUiBackendStore.unsubscribe();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const customizationsMerged = useMemo<ChatbotCustomizations>(() => {\n const templateCustomizations = customizations?.messageTemplates ?? {};\n templateCustomizations.agent = templateCustomizations.agent ?? {\n predicate: message => message.participant.isAgent,\n component: ChatbotMessageTemplateAgent,\n };\n return {\n loadingComponent: customizations?.loadingComponent,\n input: customizations?.input,\n error: customizations?.error,\n filters: customizations?.filters,\n feedback: customizations?.feedback,\n footerComponent: customizations?.footerComponent,\n messageTyping: customizations?.messageTyping ?? {\n component: ChatbotMessageTyping,\n },\n messages: [\n ...(customizations?.messages ?? []),\n {\n predicate: (message: ChatMessageModelText) =>\n !message.participant.isAgent && message.type === 'message',\n component: ChatbotMessageQuestion,\n },\n {\n predicate: (message: ChatMessageModelWelcome) => message.type === 'welcome',\n component: ChatbotMessageWelcome,\n },\n {\n predicate: (message: ChatMessageModelText) =>\n message.participant.isAgent && message.type === 'message',\n component: ChatbotMessageAnswer,\n },\n {\n predicate: (message: ChatMessageModelTimeout) => message.type === 'timeout',\n component: ChatbotMessageTimeout,\n isSystem: true,\n },\n ],\n messageTemplates: templateCustomizations,\n };\n }, [customizations]);\n\n const isFilterEnabled = Boolean(customizationsMerged.filters?.enabled);\n const isTopFilter = Boolean(\n isFilterEnabled && customizationsMerged.filters?.position === 'top'\n );\n const isBottomFilter = isFilterEnabled && !isTopFilter;\n return (\n <ChatbotToChatProviderAdapter>\n <Flex direction=\"column\" className={className} style={style}>\n {isTopFilter && <ChatFilters className=\"p-inline-6 p-block-4\" />}\n <Chat className={Styles.chat} customizations={customizationsMerged} />\n {isBottomFilter && <ChatFilters className=\"p-inline-6 p-block-end-4\" />}\n </Flex>\n </ChatbotToChatProviderAdapter>\n );\n }\n);\n"],"names":["Flex","useDependencies","Chat","ChatParticipantIcon","CHATBOT_UI_BACKEND_STORE_TOKEN","CHATBOT_UI_STORE_TOKEN","observer","useEffect","useMemo","ChatbotToChatProviderAdapter","Styles","ChatFilters","ChatbotMessageAnswer","ChatbotMessageQuestion","ChatbotMessageTimeout","ChatbotMessageTyping","ChatbotMessageWelcome","ChatbotMessageTemplateAgent","Chatbot","botIcon","botName","className","customizations","onReady","style","customizationsMerged","chatUiStore","chatUiBackendStore","init","subscribe","run","agentName","agentIcon","Bot","then","unsubscribe","templateCustomizations","messageTemplates","agent","predicate","message","participant","isAgent","component","loadingComponent","input","error","filters","feedback","footerComponent","messageTyping","messages","type","isSystem","isFilterEnabled","Boolean","enabled","isTopFilter","position","isBottomFilter","direction","chat"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/chatbot/chatbot.tsx"],"sourcesContent":["import { Flex } from '@servicetitan/anvil2';\nimport { useDependencies } from '@servicetitan/react-ioc';\nimport { Chat } from '@servicetitan/titan-chat-ui-anvil2';\nimport {\n ChatMessageModelText,\n ChatMessageModelTimeout,\n ChatMessageModelWelcome,\n ChatParticipantIcon,\n} from '@servicetitan/titan-chat-ui-common';\nimport {\n CHATBOT_UI_BACKEND_STORE_TOKEN,\n CHATBOT_UI_STORE_TOKEN,\n ChatbotCustomizations,\n isChatbotStreamingEnabled,\n} from '@servicetitan/titan-chatbot-api';\nimport { observer } from 'mobx-react';\nimport { CSSProperties, FC, useEffect, useMemo } from 'react';\nimport { ChatbotToChatProviderAdapter } from './chatbot-to-chat-provider-adapter';\nimport * as Styles from './chatbot.module.less';\nimport { ChatFilters } from './filters/chatbot-filters';\nimport { ChatbotMessageAnswer } from './messages/chatbot-message-answer';\nimport { ChatbotMessageQuestion } from './messages/chatbot-message-question';\nimport { ChatbotMessageTimeout } from './messages/chatbot-message-timeout';\nimport { ChatbotMessageTyping } from './messages/chatbot-message-typing';\nimport { ChatbotMessageWelcome } from './messages/chatbot-message-welcome';\nimport { ChatbotStreamingProgress } from './messages/chatbot-streaming-progress';\nimport { ChatbotMessageTemplateAgent } from './templates/chatbot-message-template-agent';\n\nexport interface IChatbotProps {\n className?: string;\n style?: CSSProperties;\n customizations?: ChatbotCustomizations;\n botIcon?: ChatParticipantIcon;\n botName?: string;\n onReady?: () => void;\n}\n\nexport const Chatbot: FC<IChatbotProps> = observer(\n ({ botIcon, botName, className, customizations, onReady, style }) => {\n const [chatUiStore, chatUiBackendStore] = useDependencies(\n CHATBOT_UI_STORE_TOKEN,\n CHATBOT_UI_BACKEND_STORE_TOKEN\n );\n useEffect(() => {\n const init = async () => {\n chatUiBackendStore.subscribe();\n await chatUiStore.run({\n agentName: botName ?? 'Titan',\n agentIcon: botIcon ?? ChatParticipantIcon.Bot,\n });\n onReady?.();\n };\n init().then(() => {});\n return () => chatUiBackendStore.unsubscribe();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const customizationsMerged = useMemo<ChatbotCustomizations>(() => {\n const templateCustomizations = customizations?.messageTemplates ?? {};\n templateCustomizations.agent = templateCustomizations.agent ?? {\n predicate: message => message.participant.isAgent,\n component: ChatbotMessageTemplateAgent,\n };\n return {\n loadingComponent: customizations?.loadingComponent,\n input: customizations?.input,\n error: customizations?.error,\n filters: customizations?.filters,\n feedback: customizations?.feedback,\n streaming: customizations?.streaming,\n timeouts: customizations?.timeouts,\n footerComponent: customizations?.footerComponent,\n messageTyping: customizations?.messageTyping ?? {\n component: isChatbotStreamingEnabled(customizations)\n ? ChatbotStreamingProgress\n : ChatbotMessageTyping,\n },\n messages: [\n ...(customizations?.messages ?? []),\n {\n predicate: (message: ChatMessageModelText) =>\n !message.participant.isAgent && message.type === 'message',\n component: ChatbotMessageQuestion,\n },\n {\n predicate: (message: ChatMessageModelWelcome) => message.type === 'welcome',\n component: ChatbotMessageWelcome,\n },\n {\n predicate: (message: ChatMessageModelText) =>\n message.participant.isAgent && message.type === 'message',\n component: ChatbotMessageAnswer,\n },\n {\n predicate: (message: ChatMessageModelTimeout) => message.type === 'timeout',\n component: ChatbotMessageTimeout,\n isSystem: true,\n },\n ],\n messageTemplates: templateCustomizations,\n };\n }, [customizations]);\n\n const isFilterEnabled = Boolean(customizationsMerged.filters?.enabled);\n const isTopFilter = Boolean(\n isFilterEnabled && customizationsMerged.filters?.position === 'top'\n );\n const isBottomFilter = isFilterEnabled && !isTopFilter;\n return (\n <ChatbotToChatProviderAdapter>\n <Flex direction=\"column\" className={className} style={style}>\n {isTopFilter && <ChatFilters className=\"p-inline-6 p-block-4\" />}\n <Chat className={Styles.chat} customizations={customizationsMerged} />\n {isBottomFilter && <ChatFilters className=\"p-inline-6 p-block-end-4\" />}\n </Flex>\n </ChatbotToChatProviderAdapter>\n );\n }\n);\n"],"names":["Flex","useDependencies","Chat","ChatParticipantIcon","CHATBOT_UI_BACKEND_STORE_TOKEN","CHATBOT_UI_STORE_TOKEN","isChatbotStreamingEnabled","observer","useEffect","useMemo","ChatbotToChatProviderAdapter","Styles","ChatFilters","ChatbotMessageAnswer","ChatbotMessageQuestion","ChatbotMessageTimeout","ChatbotMessageTyping","ChatbotMessageWelcome","ChatbotStreamingProgress","ChatbotMessageTemplateAgent","Chatbot","botIcon","botName","className","customizations","onReady","style","customizationsMerged","chatUiStore","chatUiBackendStore","init","subscribe","run","agentName","agentIcon","Bot","then","unsubscribe","templateCustomizations","messageTemplates","agent","predicate","message","participant","isAgent","component","loadingComponent","input","error","filters","feedback","streaming","timeouts","footerComponent","messageTyping","messages","type","isSystem","isFilterEnabled","Boolean","enabled","isTopFilter","position","isBottomFilter","direction","chat"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { IMessageTypingProps } from '@servicetitan/titan-chat-ui-anvil2';
2
+ import { FC } from 'react';
3
+ /**
4
+ * Live agent-progress indicator shown while a streamed run is in flight. Renders, top to bottom:
5
+ * the proposed plan as a checklist (done = check, active = spinner, pending = hollow circle), the
6
+ * accumulated activity log (completed lines, muted), the current status line (active, with a
7
+ * spinner), and a transient "Still working on it…" keepalive. Before any event arrives it falls
8
+ * back to the dots typing indicator so there is no silent waiting period. Replaces the typing
9
+ * component when streaming.
10
+ */
11
+ export declare const ChatbotStreamingProgress: FC<IMessageTypingProps>;
12
+ //# sourceMappingURL=chatbot-streaming-progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatbot-streaming-progress.d.ts","sourceRoot":"","sources":["../../../../src/components/chatbot/messages/chatbot-streaming-progress.tsx"],"names":[],"mappings":"AAIA,OAAO,EACH,mBAAmB,EAGtB,MAAM,oCAAoC,CAAC;AAI5C,OAAO,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAiB3B;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,EAAE,CAAC,mBAAmB,CAwE3D,CAAC"}
@@ -0,0 +1,116 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Flex, Icon, Spinner, Text } from '@servicetitan/anvil2';
3
+ import IconCheckCircle from '@servicetitan/anvil2/assets/icons/material/round/check_circle.svg';
4
+ import IconRadioUnchecked from '@servicetitan/anvil2/assets/icons/material/round/radio_button_unchecked.svg';
5
+ import { useDependencies } from '@servicetitan/react-ioc';
6
+ import { MessageAgent, MessageTyping } from '@servicetitan/titan-chat-ui-anvil2';
7
+ import { CHATBOT_UI_BACKEND_STORE_TOKEN } from '@servicetitan/titan-chatbot-api';
8
+ import { observer } from 'mobx-react';
9
+ import * as Styles from './chatbot-streaming-progress.module.less';
10
+ const mutedStyle = {
11
+ opacity: 0.6
12
+ };
13
+ const keepaliveStyle = {
14
+ opacity: 0.6,
15
+ fontStyle: 'italic'
16
+ };
17
+ /** Status marker for a single plan step: check when done, spinner when active, hollow circle when pending. */ const StepMarker = ({ status })=>{
18
+ if (status === 'active') {
19
+ return /*#__PURE__*/ _jsx(Spinner, {
20
+ size: "small",
21
+ inherit: true
22
+ });
23
+ }
24
+ if (status === 'done') {
25
+ return /*#__PURE__*/ _jsx(Icon, {
26
+ svg: IconCheckCircle,
27
+ className: "c-status-success"
28
+ });
29
+ }
30
+ return /*#__PURE__*/ _jsx(Icon, {
31
+ svg: IconRadioUnchecked,
32
+ style: mutedStyle
33
+ });
34
+ };
35
+ /**
36
+ * Live agent-progress indicator shown while a streamed run is in flight. Renders, top to bottom:
37
+ * the proposed plan as a checklist (done = check, active = spinner, pending = hollow circle), the
38
+ * accumulated activity log (completed lines, muted), the current status line (active, with a
39
+ * spinner), and a transient "Still working on it…" keepalive. Before any event arrives it falls
40
+ * back to the dots typing indicator so there is no silent waiting period. Replaces the typing
41
+ * component when streaming.
42
+ */ export const ChatbotStreamingProgress = observer(({ avatar })=>{
43
+ const [backendStore] = useDependencies(CHATBOT_UI_BACKEND_STORE_TOKEN);
44
+ const { keepaliveText, logLines, statusText, steps } = backendStore.streamingProgress;
45
+ const hasProgress = steps.length > 0 || logLines.length > 0 || Boolean(statusText) || Boolean(keepaliveText);
46
+ if (!hasProgress) {
47
+ // No event yet β€” show the animated typing indicator (immediate, non-silent feedback).
48
+ return /*#__PURE__*/ _jsx(MessageTyping, {
49
+ avatar: avatar
50
+ });
51
+ }
52
+ return /*#__PURE__*/ _jsx(MessageAgent, {
53
+ avatar: avatar,
54
+ subtle: true,
55
+ children: /*#__PURE__*/ _jsxs(Flex, {
56
+ direction: "column",
57
+ gap: 1,
58
+ className: Styles.container,
59
+ "data-cy": "titan-chatbot-streaming-progress",
60
+ children: [
61
+ steps.length > 0 && /*#__PURE__*/ _jsx(Flex, {
62
+ direction: "column",
63
+ gap: 1,
64
+ "data-cy": "titan-chatbot-streaming-steps",
65
+ children: steps.map((step)=>/*#__PURE__*/ _jsxs(Flex, {
66
+ gap: 1,
67
+ alignItems: "center",
68
+ "data-cy": "titan-chatbot-streaming-step",
69
+ "data-status": step.status,
70
+ children: [
71
+ /*#__PURE__*/ _jsx(StepMarker, {
72
+ status: step.status
73
+ }),
74
+ /*#__PURE__*/ _jsx(Text, {
75
+ subdued: step.status !== 'active',
76
+ style: step.status === 'pending' ? mutedStyle : undefined,
77
+ children: step.title
78
+ })
79
+ ]
80
+ }, step.id))
81
+ }),
82
+ logLines.length > 0 && /*#__PURE__*/ _jsx(Flex, {
83
+ direction: "column",
84
+ "data-cy": "titan-chatbot-streaming-log",
85
+ children: logLines.map((line, index)=>/*#__PURE__*/ _jsx(Text, {
86
+ style: mutedStyle,
87
+ "data-cy": "titan-chatbot-streaming-log-line",
88
+ children: line
89
+ }, index))
90
+ }),
91
+ statusText && /*#__PURE__*/ _jsxs(Flex, {
92
+ gap: 1,
93
+ alignItems: "center",
94
+ "data-cy": "titan-chatbot-streaming-status",
95
+ children: [
96
+ /*#__PURE__*/ _jsx(Spinner, {
97
+ size: "small",
98
+ inherit: true
99
+ }),
100
+ /*#__PURE__*/ _jsx(Text, {
101
+ subdued: true,
102
+ children: statusText
103
+ })
104
+ ]
105
+ }),
106
+ keepaliveText && /*#__PURE__*/ _jsx(Text, {
107
+ style: keepaliveStyle,
108
+ "data-cy": "titan-chatbot-streaming-keepalive",
109
+ children: keepaliveText
110
+ })
111
+ ]
112
+ })
113
+ });
114
+ });
115
+
116
+ //# sourceMappingURL=chatbot-streaming-progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/chatbot/messages/chatbot-streaming-progress.tsx"],"sourcesContent":["import { Flex, Icon, Spinner, Text } from '@servicetitan/anvil2';\nimport IconCheckCircle from '@servicetitan/anvil2/assets/icons/material/round/check_circle.svg';\nimport IconRadioUnchecked from '@servicetitan/anvil2/assets/icons/material/round/radio_button_unchecked.svg';\nimport { useDependencies } from '@servicetitan/react-ioc';\nimport {\n IMessageTypingProps,\n MessageAgent,\n MessageTyping,\n} from '@servicetitan/titan-chat-ui-anvil2';\nimport { StreamingStep } from '@servicetitan/titan-chat-ui-common';\nimport { CHATBOT_UI_BACKEND_STORE_TOKEN } from '@servicetitan/titan-chatbot-api';\nimport { observer } from 'mobx-react';\nimport { FC } from 'react';\nimport * as Styles from './chatbot-streaming-progress.module.less';\n\nconst mutedStyle = { opacity: 0.6 } as const;\nconst keepaliveStyle = { opacity: 0.6, fontStyle: 'italic' } as const;\n\n/** Status marker for a single plan step: check when done, spinner when active, hollow circle when pending. */\nconst StepMarker: FC<{ status: StreamingStep['status'] }> = ({ status }) => {\n if (status === 'active') {\n return <Spinner size=\"small\" inherit />;\n }\n if (status === 'done') {\n return <Icon svg={IconCheckCircle} className=\"c-status-success\" />;\n }\n return <Icon svg={IconRadioUnchecked} style={mutedStyle} />;\n};\n\n/**\n * Live agent-progress indicator shown while a streamed run is in flight. Renders, top to bottom:\n * the proposed plan as a checklist (done = check, active = spinner, pending = hollow circle), the\n * accumulated activity log (completed lines, muted), the current status line (active, with a\n * spinner), and a transient \"Still working on it…\" keepalive. Before any event arrives it falls\n * back to the dots typing indicator so there is no silent waiting period. Replaces the typing\n * component when streaming.\n */\nexport const ChatbotStreamingProgress: FC<IMessageTypingProps> = observer(({ avatar }) => {\n const [backendStore] = useDependencies(CHATBOT_UI_BACKEND_STORE_TOKEN);\n const { keepaliveText, logLines, statusText, steps } = backendStore.streamingProgress;\n\n const hasProgress =\n steps.length > 0 || logLines.length > 0 || Boolean(statusText) || Boolean(keepaliveText);\n if (!hasProgress) {\n // No event yet β€” show the animated typing indicator (immediate, non-silent feedback).\n return <MessageTyping avatar={avatar} />;\n }\n\n return (\n <MessageAgent avatar={avatar} subtle>\n <Flex\n direction=\"column\"\n gap={1}\n className={Styles.container}\n data-cy=\"titan-chatbot-streaming-progress\"\n >\n {steps.length > 0 && (\n <Flex direction=\"column\" gap={1} data-cy=\"titan-chatbot-streaming-steps\">\n {steps.map(step => (\n <Flex\n key={step.id}\n gap={1}\n alignItems=\"center\"\n data-cy=\"titan-chatbot-streaming-step\"\n data-status={step.status}\n >\n <StepMarker status={step.status} />\n <Text\n subdued={step.status !== 'active'}\n style={step.status === 'pending' ? mutedStyle : undefined}\n >\n {step.title}\n </Text>\n </Flex>\n ))}\n </Flex>\n )}\n\n {logLines.length > 0 && (\n <Flex direction=\"column\" data-cy=\"titan-chatbot-streaming-log\">\n {logLines.map((line, index) => (\n <Text\n // Activity log is append-only and never reordered, so the index is a stable key.\n // eslint-disable-next-line react/no-array-index-key\n key={index}\n style={mutedStyle}\n data-cy=\"titan-chatbot-streaming-log-line\"\n >\n {line}\n </Text>\n ))}\n </Flex>\n )}\n\n {statusText && (\n <Flex gap={1} alignItems=\"center\" data-cy=\"titan-chatbot-streaming-status\">\n <Spinner size=\"small\" inherit />\n <Text subdued>{statusText}</Text>\n </Flex>\n )}\n\n {keepaliveText && (\n <Text style={keepaliveStyle} data-cy=\"titan-chatbot-streaming-keepalive\">\n {keepaliveText}\n </Text>\n )}\n </Flex>\n </MessageAgent>\n );\n});\n"],"names":["Flex","Icon","Spinner","Text","IconCheckCircle","IconRadioUnchecked","useDependencies","MessageAgent","MessageTyping","CHATBOT_UI_BACKEND_STORE_TOKEN","observer","Styles","mutedStyle","opacity","keepaliveStyle","fontStyle","StepMarker","status","size","inherit","svg","className","style","ChatbotStreamingProgress","avatar","backendStore","keepaliveText","logLines","statusText","steps","streamingProgress","hasProgress","length","Boolean","subtle","direction","gap","container","data-cy","map","step","alignItems","data-status","subdued","undefined","title","id","line","index"],"mappings":";AAAA,SAASA,IAAI,EAAEC,IAAI,EAAEC,OAAO,EAAEC,IAAI,QAAQ,uBAAuB;AACjE,OAAOC,qBAAqB,oEAAoE;AAChG,OAAOC,wBAAwB,8EAA8E;AAC7G,SAASC,eAAe,QAAQ,0BAA0B;AAC1D,SAEIC,YAAY,EACZC,aAAa,QACV,qCAAqC;AAE5C,SAASC,8BAA8B,QAAQ,kCAAkC;AACjF,SAASC,QAAQ,QAAQ,aAAa;AAEtC,YAAYC,YAAY,2CAA2C;AAEnE,MAAMC,aAAa;IAAEC,SAAS;AAAI;AAClC,MAAMC,iBAAiB;IAAED,SAAS;IAAKE,WAAW;AAAS;AAE3D,4GAA4G,GAC5G,MAAMC,aAAsD,CAAC,EAAEC,MAAM,EAAE;IACnE,IAAIA,WAAW,UAAU;QACrB,qBAAO,KAACf;YAAQgB,MAAK;YAAQC,OAAO;;IACxC;IACA,IAAIF,WAAW,QAAQ;QACnB,qBAAO,KAAChB;YAAKmB,KAAKhB;YAAiBiB,WAAU;;IACjD;IACA,qBAAO,KAACpB;QAAKmB,KAAKf;QAAoBiB,OAAOV;;AACjD;AAEA;;;;;;;CAOC,GACD,OAAO,MAAMW,2BAAoDb,SAAS,CAAC,EAAEc,MAAM,EAAE;IACjF,MAAM,CAACC,aAAa,GAAGnB,gBAAgBG;IACvC,MAAM,EAAEiB,aAAa,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,KAAK,EAAE,GAAGJ,aAAaK,iBAAiB;IAErF,MAAMC,cACFF,MAAMG,MAAM,GAAG,KAAKL,SAASK,MAAM,GAAG,KAAKC,QAAQL,eAAeK,QAAQP;IAC9E,IAAI,CAACK,aAAa;QACd,sFAAsF;QACtF,qBAAO,KAACvB;YAAcgB,QAAQA;;IAClC;IAEA,qBACI,KAACjB;QAAaiB,QAAQA;QAAQU,MAAM;kBAChC,cAAA,MAAClC;YACGmC,WAAU;YACVC,KAAK;YACLf,WAAWV,OAAO0B,SAAS;YAC3BC,WAAQ;;gBAEPT,MAAMG,MAAM,GAAG,mBACZ,KAAChC;oBAAKmC,WAAU;oBAASC,KAAK;oBAAGE,WAAQ;8BACpCT,MAAMU,GAAG,CAACC,CAAAA,qBACP,MAACxC;4BAEGoC,KAAK;4BACLK,YAAW;4BACXH,WAAQ;4BACRI,eAAaF,KAAKvB,MAAM;;8CAExB,KAACD;oCAAWC,QAAQuB,KAAKvB,MAAM;;8CAC/B,KAACd;oCACGwC,SAASH,KAAKvB,MAAM,KAAK;oCACzBK,OAAOkB,KAAKvB,MAAM,KAAK,YAAYL,aAAagC;8CAE/CJ,KAAKK,KAAK;;;2BAXVL,KAAKM,EAAE;;gBAkB3BnB,SAASK,MAAM,GAAG,mBACf,KAAChC;oBAAKmC,WAAU;oBAASG,WAAQ;8BAC5BX,SAASY,GAAG,CAAC,CAACQ,MAAMC,sBACjB,KAAC7C;4BAIGmB,OAAOV;4BACP0B,WAAQ;sCAEPS;2BAJIC;;gBAUpBpB,4BACG,MAAC5B;oBAAKoC,KAAK;oBAAGK,YAAW;oBAASH,WAAQ;;sCACtC,KAACpC;4BAAQgB,MAAK;4BAAQC,OAAO;;sCAC7B,KAAChB;4BAAKwC,OAAO;sCAAEf;;;;gBAItBF,+BACG,KAACvB;oBAAKmB,OAAOR;oBAAgBwB,WAAQ;8BAChCZ;;;;;AAMzB,GAAG"}
@@ -0,0 +1,4 @@
1
+ .container {
2
+ height: 100%;
3
+ justify-content: center;
4
+ }
@@ -0,0 +1,3 @@
1
+ export const __esModule: true;
2
+ export const container: string;
3
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/titan-chatbot-ui-anvil2",
3
- "version": "8.0.0",
3
+ "version": "9.0.0",
4
4
  "description": "Chatbot experience UI package (Anvil2 version)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,9 +17,9 @@
17
17
  "push:local": "yalc push"
18
18
  },
19
19
  "dependencies": {
20
- "@servicetitan/titan-chat-ui-anvil2": "^8.0.0",
21
- "@servicetitan/titan-chat-ui-common": "^8.0.0",
22
- "@servicetitan/titan-chatbot-api": "^8.0.0",
20
+ "@servicetitan/titan-chat-ui-anvil2": "^9.0.0",
21
+ "@servicetitan/titan-chat-ui-common": "^9.0.0",
22
+ "@servicetitan/titan-chatbot-api": "^9.0.0",
23
23
  "nanoid": "^5.1.5"
24
24
  },
25
25
  "peerDependencies": {
@@ -36,7 +36,7 @@
36
36
  "react-dom": ">=18"
37
37
  },
38
38
  "devDependencies": {
39
- "@servicetitan/cypress-shared": "^8.0.0",
39
+ "@servicetitan/cypress-shared": "^9.0.0",
40
40
  "cypress": "^15.12.0"
41
41
  },
42
42
  "keywords": [
@@ -49,5 +49,5 @@
49
49
  "cli": {
50
50
  "webpack": false
51
51
  },
52
- "gitHead": "3af2e2dd99b7d735dbb763075de074986f866ce3"
52
+ "gitHead": "028073fe571014741da70bcff173d617e2738125"
53
53
  }
@@ -2,6 +2,6 @@ import { AnvilProvider } from '@servicetitan/anvil2';
2
2
  import { runChatbotLiveSharedTests } from '@servicetitan/cypress-shared';
3
3
  import { Chatbot } from '../chatbot';
4
4
 
5
- describe('[Chatbot]', () => {
5
+ describe.skip('[Chatbot]', () => {
6
6
  runChatbotLiveSharedTests(Chatbot, component => <AnvilProvider>{component}</AnvilProvider>);
7
7
  });
@@ -0,0 +1,9 @@
1
+ import { AnvilProvider } from '@servicetitan/anvil2';
2
+ import { runChatbotStreamingSharedTests } from '@servicetitan/cypress-shared';
3
+ import { Chatbot } from '../chatbot';
4
+
5
+ describe('[Chatbot streaming]', () => {
6
+ runChatbotStreamingSharedTests(Chatbot, component => (
7
+ <AnvilProvider>{component}</AnvilProvider>
8
+ ));
9
+ });
@@ -11,6 +11,7 @@ import {
11
11
  CHATBOT_UI_BACKEND_STORE_TOKEN,
12
12
  CHATBOT_UI_STORE_TOKEN,
13
13
  ChatbotCustomizations,
14
+ isChatbotStreamingEnabled,
14
15
  } from '@servicetitan/titan-chatbot-api';
15
16
  import { observer } from 'mobx-react';
16
17
  import { CSSProperties, FC, useEffect, useMemo } from 'react';
@@ -22,6 +23,7 @@ import { ChatbotMessageQuestion } from './messages/chatbot-message-question';
22
23
  import { ChatbotMessageTimeout } from './messages/chatbot-message-timeout';
23
24
  import { ChatbotMessageTyping } from './messages/chatbot-message-typing';
24
25
  import { ChatbotMessageWelcome } from './messages/chatbot-message-welcome';
26
+ import { ChatbotStreamingProgress } from './messages/chatbot-streaming-progress';
25
27
  import { ChatbotMessageTemplateAgent } from './templates/chatbot-message-template-agent';
26
28
 
27
29
  export interface IChatbotProps {
@@ -65,9 +67,13 @@ export const Chatbot: FC<IChatbotProps> = observer(
65
67
  error: customizations?.error,
66
68
  filters: customizations?.filters,
67
69
  feedback: customizations?.feedback,
70
+ streaming: customizations?.streaming,
71
+ timeouts: customizations?.timeouts,
68
72
  footerComponent: customizations?.footerComponent,
69
73
  messageTyping: customizations?.messageTyping ?? {
70
- component: ChatbotMessageTyping,
74
+ component: isChatbotStreamingEnabled(customizations)
75
+ ? ChatbotStreamingProgress
76
+ : ChatbotMessageTyping,
71
77
  },
72
78
  messages: [
73
79
  ...(customizations?.messages ?? []),
@@ -0,0 +1,4 @@
1
+ .container {
2
+ height: 100%;
3
+ justify-content: center;
4
+ }
@@ -0,0 +1,3 @@
1
+ export const __esModule: true;
2
+ export const container: string;
3
+
@@ -0,0 +1,110 @@
1
+ import { Flex, Icon, Spinner, Text } from '@servicetitan/anvil2';
2
+ import IconCheckCircle from '@servicetitan/anvil2/assets/icons/material/round/check_circle.svg';
3
+ import IconRadioUnchecked from '@servicetitan/anvil2/assets/icons/material/round/radio_button_unchecked.svg';
4
+ import { useDependencies } from '@servicetitan/react-ioc';
5
+ import {
6
+ IMessageTypingProps,
7
+ MessageAgent,
8
+ MessageTyping,
9
+ } from '@servicetitan/titan-chat-ui-anvil2';
10
+ import { StreamingStep } from '@servicetitan/titan-chat-ui-common';
11
+ import { CHATBOT_UI_BACKEND_STORE_TOKEN } from '@servicetitan/titan-chatbot-api';
12
+ import { observer } from 'mobx-react';
13
+ import { FC } from 'react';
14
+ import * as Styles from './chatbot-streaming-progress.module.less';
15
+
16
+ const mutedStyle = { opacity: 0.6 } as const;
17
+ const keepaliveStyle = { opacity: 0.6, fontStyle: 'italic' } as const;
18
+
19
+ /** Status marker for a single plan step: check when done, spinner when active, hollow circle when pending. */
20
+ const StepMarker: FC<{ status: StreamingStep['status'] }> = ({ status }) => {
21
+ if (status === 'active') {
22
+ return <Spinner size="small" inherit />;
23
+ }
24
+ if (status === 'done') {
25
+ return <Icon svg={IconCheckCircle} className="c-status-success" />;
26
+ }
27
+ return <Icon svg={IconRadioUnchecked} style={mutedStyle} />;
28
+ };
29
+
30
+ /**
31
+ * Live agent-progress indicator shown while a streamed run is in flight. Renders, top to bottom:
32
+ * the proposed plan as a checklist (done = check, active = spinner, pending = hollow circle), the
33
+ * accumulated activity log (completed lines, muted), the current status line (active, with a
34
+ * spinner), and a transient "Still working on it…" keepalive. Before any event arrives it falls
35
+ * back to the dots typing indicator so there is no silent waiting period. Replaces the typing
36
+ * component when streaming.
37
+ */
38
+ export const ChatbotStreamingProgress: FC<IMessageTypingProps> = observer(({ avatar }) => {
39
+ const [backendStore] = useDependencies(CHATBOT_UI_BACKEND_STORE_TOKEN);
40
+ const { keepaliveText, logLines, statusText, steps } = backendStore.streamingProgress;
41
+
42
+ const hasProgress =
43
+ steps.length > 0 || logLines.length > 0 || Boolean(statusText) || Boolean(keepaliveText);
44
+ if (!hasProgress) {
45
+ // No event yet β€” show the animated typing indicator (immediate, non-silent feedback).
46
+ return <MessageTyping avatar={avatar} />;
47
+ }
48
+
49
+ return (
50
+ <MessageAgent avatar={avatar} subtle>
51
+ <Flex
52
+ direction="column"
53
+ gap={1}
54
+ className={Styles.container}
55
+ data-cy="titan-chatbot-streaming-progress"
56
+ >
57
+ {steps.length > 0 && (
58
+ <Flex direction="column" gap={1} data-cy="titan-chatbot-streaming-steps">
59
+ {steps.map(step => (
60
+ <Flex
61
+ key={step.id}
62
+ gap={1}
63
+ alignItems="center"
64
+ data-cy="titan-chatbot-streaming-step"
65
+ data-status={step.status}
66
+ >
67
+ <StepMarker status={step.status} />
68
+ <Text
69
+ subdued={step.status !== 'active'}
70
+ style={step.status === 'pending' ? mutedStyle : undefined}
71
+ >
72
+ {step.title}
73
+ </Text>
74
+ </Flex>
75
+ ))}
76
+ </Flex>
77
+ )}
78
+
79
+ {logLines.length > 0 && (
80
+ <Flex direction="column" data-cy="titan-chatbot-streaming-log">
81
+ {logLines.map((line, index) => (
82
+ <Text
83
+ // Activity log is append-only and never reordered, so the index is a stable key.
84
+ // eslint-disable-next-line react/no-array-index-key
85
+ key={index}
86
+ style={mutedStyle}
87
+ data-cy="titan-chatbot-streaming-log-line"
88
+ >
89
+ {line}
90
+ </Text>
91
+ ))}
92
+ </Flex>
93
+ )}
94
+
95
+ {statusText && (
96
+ <Flex gap={1} alignItems="center" data-cy="titan-chatbot-streaming-status">
97
+ <Spinner size="small" inherit />
98
+ <Text subdued>{statusText}</Text>
99
+ </Flex>
100
+ )}
101
+
102
+ {keepaliveText && (
103
+ <Text style={keepaliveStyle} data-cy="titan-chatbot-streaming-keepalive">
104
+ {keepaliveText}
105
+ </Text>
106
+ )}
107
+ </Flex>
108
+ </MessageAgent>
109
+ );
110
+ });