@anker-in/campaign-ui 0.0.33-alpha2 → 0.0.33-alpha3

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 (85) hide show
  1. package/dist/cjs/components/chat/Markdown.d.ts +5 -0
  2. package/dist/cjs/components/chat/Messages.d.ts +3 -0
  3. package/dist/cjs/components/chat/Response.d.ts +2 -0
  4. package/dist/cjs/components/chat/action.d.ts +6 -2
  5. package/dist/cjs/components/chat/action.js +1 -1
  6. package/dist/cjs/components/chat/action.js.map +3 -3
  7. package/dist/cjs/components/chat/button.d.ts +3 -0
  8. package/dist/cjs/components/chat/button.js +2 -0
  9. package/dist/cjs/components/chat/button.js.map +7 -0
  10. package/dist/cjs/components/chat/index.d.ts +16 -2
  11. package/dist/cjs/components/chat/index.js +1 -5
  12. package/dist/cjs/components/chat/index.js.map +3 -3
  13. package/dist/cjs/components/chat/markdown.js +2 -0
  14. package/dist/cjs/components/chat/markdown.js.map +7 -0
  15. package/dist/cjs/components/chat/marksdown.d.ts +5 -0
  16. package/dist/cjs/components/chat/message.d.ts +2 -0
  17. package/dist/cjs/components/chat/messages.js +2 -0
  18. package/dist/cjs/components/chat/messages.js.map +7 -0
  19. package/dist/cjs/components/chat/props.d.ts +43 -0
  20. package/dist/cjs/components/chat/props.js +2 -0
  21. package/dist/cjs/components/chat/props.js.map +7 -0
  22. package/dist/cjs/components/chat/response copy.d.ts +2 -0
  23. package/dist/cjs/components/chat/response.js +2 -0
  24. package/dist/cjs/components/chat/response.js.map +7 -0
  25. package/dist/cjs/components/chat/rresponse.d.ts +2 -0
  26. package/dist/cjs/components/chat/suggestions.d.ts +3 -0
  27. package/dist/cjs/components/chat/suggestions.js +2 -0
  28. package/dist/cjs/components/chat/suggestions.js.map +7 -0
  29. package/dist/cjs/stories/chat.stories.d.ts +1 -0
  30. package/dist/cjs/stories/chat.stories.js +1 -1
  31. package/dist/cjs/stories/chat.stories.js.map +3 -3
  32. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  33. package/dist/esm/components/chat/Markdown.d.ts +5 -0
  34. package/dist/esm/components/chat/Messages.d.ts +3 -0
  35. package/dist/esm/components/chat/Response.d.ts +2 -0
  36. package/dist/esm/components/chat/action.d.ts +6 -2
  37. package/dist/esm/components/chat/action.js +1 -1
  38. package/dist/esm/components/chat/action.js.map +3 -3
  39. package/dist/esm/components/chat/button.d.ts +3 -0
  40. package/dist/esm/components/chat/button.js +2 -0
  41. package/dist/esm/components/chat/button.js.map +7 -0
  42. package/dist/esm/components/chat/index.d.ts +16 -2
  43. package/dist/esm/components/chat/index.js +1 -5
  44. package/dist/esm/components/chat/index.js.map +3 -3
  45. package/dist/esm/components/chat/markdown.js +2 -0
  46. package/dist/esm/components/chat/markdown.js.map +7 -0
  47. package/dist/esm/components/chat/marksdown.d.ts +5 -0
  48. package/dist/esm/components/chat/message.d.ts +2 -0
  49. package/dist/esm/components/chat/messages.js +2 -0
  50. package/dist/esm/components/chat/messages.js.map +7 -0
  51. package/dist/esm/components/chat/props.d.ts +43 -0
  52. package/dist/esm/components/chat/props.js +2 -0
  53. package/dist/esm/components/chat/props.js.map +7 -0
  54. package/dist/esm/components/chat/response copy.d.ts +2 -0
  55. package/dist/esm/components/chat/response.js +2 -0
  56. package/dist/esm/components/chat/response.js.map +7 -0
  57. package/dist/esm/components/chat/rresponse.d.ts +2 -0
  58. package/dist/esm/components/chat/suggestions.d.ts +3 -0
  59. package/dist/esm/components/chat/suggestions.js +2 -0
  60. package/dist/esm/components/chat/suggestions.js.map +7 -0
  61. package/dist/esm/stories/chat.stories.d.ts +1 -0
  62. package/dist/esm/stories/chat.stories.js +1 -1
  63. package/dist/esm/stories/chat.stories.js.map +2 -2
  64. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +1 -1
  66. package/src/components/chat/action.tsx +32 -11
  67. package/src/components/chat/button.tsx +23 -0
  68. package/src/components/chat/index.tsx +77 -67
  69. package/src/components/chat/markdown.tsx +36 -0
  70. package/src/components/chat/messages.tsx +167 -0
  71. package/src/components/chat/props.ts +51 -0
  72. package/src/components/chat/response.tsx +17 -0
  73. package/src/components/chat/suggestions.tsx +34 -0
  74. package/src/stories/chat.stories.tsx +4 -4
  75. package/src/styles/css/messages.css +4 -0
  76. package/src/styles/css/suggestions.css +1 -0
  77. package/dist/cjs/components/chat/chatContext.js +0 -2
  78. package/dist/cjs/components/chat/chatContext.js.map +0 -7
  79. package/dist/cjs/components/theme.js +0 -2
  80. package/dist/cjs/components/theme.js.map +0 -7
  81. package/dist/esm/components/chat/chatContext.js +0 -2
  82. package/dist/esm/components/chat/chatContext.js.map +0 -7
  83. package/dist/esm/components/theme.js +0 -2
  84. package/dist/esm/components/theme.js.map +0 -7
  85. package/src/components/chat/chatContext.tsx +0 -161
@@ -1,29 +1,47 @@
1
1
  import React from 'react'
2
2
  import { useEffect } from 'react'
3
3
  import { useCopilotChat, useCopilotAction, useCopilotReadable } from '@copilotkit/react-core'
4
- import { useCopilotChatSuggestions } from '@copilotkit/react-ui'
5
- import { Role, TextMessage } from '@copilotkit/runtime-client-gql'
4
+ import { Role, ActionExecutionMessage, TextMessage } from '@copilotkit/runtime-client-gql'
6
5
 
7
6
  interface Message {
8
7
  content: string
9
8
  role: string
9
+ name?: string
10
+ scope?: any
11
+ arguments?: any
10
12
  }
11
13
 
12
14
  export interface ActionProps {
13
15
  start?: string
14
16
  history: Message[]
15
- buynowRender?: string | ((_props: any) => React.ReactElement)
17
+ addtocartRender?: string | ((_props: any) => React.ReactElement)
18
+ productRender?: string | ((_props: any) => React.ReactElement)
16
19
  signupRender?: string | ((_props: any) => React.ReactElement)
17
20
  children?: React.ReactNode
18
21
  }
19
22
 
20
- export const CopilotAction = ({ history, buynowRender, signupRender, children }: ActionProps) => {
23
+ export const CopilotAction = ({
24
+ start,
25
+ history,
26
+ addtocartRender,
27
+ productRender,
28
+ signupRender,
29
+ children,
30
+ }: ActionProps) => {
21
31
  const { setMessages } = useCopilotChat()
22
32
 
23
33
  useEffect(() => {
24
- const originhistory = history?.filter(value => value?.content)
34
+ const originhistory = history?.filter(value => value?.content || value?.arguments)
25
35
  if (originhistory?.length > 0) {
26
36
  const content = originhistory?.map(value => {
37
+ if (value?.name && value?.arguments) {
38
+ console.log('value?.name && value?.arguments', value?.name, value?.arguments)
39
+ return new ActionExecutionMessage({
40
+ name: value?.name,
41
+ arguments: value?.arguments,
42
+ scope: value?.scope,
43
+ })
44
+ }
27
45
  return new TextMessage({
28
46
  ...value,
29
47
  role: value?.role === 'user' ? Role.User : Role.Assistant,
@@ -33,18 +51,21 @@ export const CopilotAction = ({ history, buynowRender, signupRender, children }:
33
51
  }
34
52
  }, [history])
35
53
 
36
- useCopilotChatSuggestions(
37
- {
38
- instructions: 'Suggest the most relevant next actions.',
54
+ useCopilotAction({
55
+ name: 'add_cart',
56
+ description: 'add tocart',
57
+ parameters: [{ sku: '', handle: '' }] as any,
58
+ render: addtocartRender || (props => <div>{JSON.stringify(props)}</div>),
59
+ handler: function () {
60
+ // console.log('buynow-props', props)
39
61
  },
40
- []
41
- )
62
+ })
42
63
 
43
64
  useCopilotAction({
44
65
  name: 'buynow',
45
66
  description: 'buy now',
46
67
  parameters: [{ sku: '', handle: '' }] as any,
47
- render: buynowRender || (props => <div>{JSON.stringify(props)}</div>),
68
+ render: productRender || (props => <div>{JSON.stringify(props)}</div>),
48
69
  handler: function () {
49
70
  // console.log('buynow-props', props)
50
71
  },
@@ -0,0 +1,23 @@
1
+ import { useChatContext } from '@copilotkit/react-ui'
2
+
3
+ import type { ButtonProps } from './props.js'
4
+
5
+ const DefaultButton = ({ setOpen, setOpenTip }: ButtonProps) => {
6
+ const context = useChatContext()
7
+
8
+ return (
9
+ <button
10
+ onClick={() => {
11
+ setOpen(true)
12
+ setOpenTip(false)
13
+ }}
14
+ className="copilotKitButton "
15
+ aria-label="Open Chat"
16
+ >
17
+ <div className="copilotKitButtonIcon copilotKitButtonIconOpen">{context.icons.openIcon}</div>
18
+ <div className="copilotKitButtonIcon copilotKitButtonIconClose">{context.icons.closeIcon}</div>
19
+ </button>
20
+ )
21
+ }
22
+
23
+ export default DefaultButton
@@ -1,12 +1,15 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ import React from 'react'
1
3
  import { useCallback, useState, useEffect } from 'react'
2
4
  import { CopilotKit } from '@copilotkit/react-core'
3
5
  import { CopilotPopup } from '@copilotkit/react-ui'
4
-
6
+ import DefaultButton from './button.js'
7
+ import Messages from './messages.js'
8
+ import ResponseButton from './response.js'
9
+ import Suggestions from './suggestions.js'
5
10
  import { CopilotAction } from './action.js'
6
11
  import fetcher from '../../helpers/fetcher.js'
7
12
 
8
- // import '../../styles/chat.css'
9
-
10
13
  export interface ChatProps {
11
14
  title: string
12
15
  runtimeUrl: string
@@ -14,20 +17,33 @@ export interface ChatProps {
14
17
  /** GA 的 client id
15
18
  */
16
19
  user_id: string
20
+ /** 是否登陆用户 0 or 1
21
+ */
22
+ account?: string
23
+ /** 用户浏览器语言
24
+ */
25
+ locale?: string
17
26
  /** ?a=1&b=2
18
27
  */
19
28
  query?: string
29
+ /** 'follow' or 'unfollow' | true or false
30
+ */
31
+ showResponseButton?: string | boolean
32
+ /** 加购卡片,接受参数: {"status":"complete","args":[{"sku":"A3936031","handle":"space-a40-a3936031"}],"result":""}
33
+ * 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
34
+ */
35
+ addtocartRender?: string | ((_props: any) => React.ReactElement)
20
36
  /** 产品卡片,接受参数: {"status":"complete","args":[{"sku":"A3936031","handle":"space-a40-a3936031"}],"result":""}
21
37
  * 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
22
38
  */
23
- buynowRender?: string | ((_props: any) => React.ReactElement)
39
+ productRender?: string | ((_props: any) => React.ReactElement)
24
40
  /** 订阅卡片,接受参数:
25
41
  * 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
26
42
  */
27
43
  signupRender?: string | ((_props: any) => React.ReactElement)
28
44
  /** CopilotPopup 自定义参数,参考:https://docs.copilotkit.ai/reference/components/CopilotPopup */
29
45
  popup?: any
30
- /** 文案,{"popupTip": "Hi ! Welcome to soundcore Innovations live chat!"} */
46
+ /** 文案,{"popupTip": "Hi ! Welcome to soundcore Innovations live chat!", "popupTipTimeout": [3000, 6000]} */
31
47
  lang?: any
32
48
  /** 参考此文档:https://docs.copilotkit.ai/concepts/customize-look-and-feel */
33
49
  style?: any
@@ -41,38 +57,60 @@ const Chat = (props: ChatProps) => {
41
57
  popup,
42
58
  shopify_domain,
43
59
  user_id = '',
60
+ account = '',
61
+ locale = '',
44
62
  query = '',
45
- buynowRender,
63
+ showResponseButton,
64
+ addtocartRender,
65
+ productRender,
46
66
  signupRender,
47
67
  style,
48
68
  className = '',
49
69
  lang,
50
70
  } = props
71
+ const [autoTipOpen, autoTipClose] = lang?.popupTipTimeout || [3000, 6000]
51
72
 
52
73
  const [openTip, setOpenTip] = useState(false)
53
74
  const [openPopup, setOpenPopup] = useState(false)
54
75
  const [start, setstart] = useState('')
76
+ const [currentSuggestions, setCurrentSuggestions] = useState([])
55
77
  const [history, sethistory] = useState<[{ content: string; role: string }] | []>([])
56
78
 
57
79
  const getStart = useCallback(async () => {
58
80
  const result = await fetcher({
59
- url: `${runtimeUrl}/copilotkit/start`,
81
+ url: `${runtimeUrl}/copilotkit/start${query}`,
60
82
  method: 'GET',
61
- headers: { user_id: user_id || '', shopify_domain: shopify_domain },
83
+ headers: { user_id: user_id || '', shopify_domain, account, locale },
62
84
  })
63
85
 
86
+ setCurrentSuggestions(result?.suggested_questions?.map((item: string) => ({ message: item, title: item })) || [])
64
87
  setstart(result?.opening_statement || '')
65
88
  sethistory(result?.history || [])
66
- }, [user_id])
89
+ }, [user_id, shopify_domain, account, locale])
90
+
91
+ const getSuggestions = useCallback(async () => {
92
+ const result = await fetcher({
93
+ url: `${runtimeUrl}/copilotkit/suggested_questions${query}`,
94
+ method: 'GET',
95
+ headers: { user_id: user_id || '', shopify_domain, account, locale },
96
+ })
97
+
98
+ setCurrentSuggestions(
99
+ result?.suggested_questions?.data?.map((item: string) => ({ message: item, title: item })) || []
100
+ )
101
+ }, [user_id, shopify_domain, account, locale])
67
102
 
68
103
  const setOpenTipFn = () => {
69
104
  if (!openTip) setOpenTip(true)
105
+ setTimeout(() => {
106
+ setOpenTip(false)
107
+ }, autoTipClose)
70
108
  }
71
109
 
72
110
  useEffect(() => {
73
111
  const timer = setTimeout(() => {
74
112
  setOpenTipFn()
75
- }, 3000)
113
+ }, autoTipOpen)
76
114
 
77
115
  return () => {
78
116
  clearTimeout(timer)
@@ -87,32 +125,39 @@ const Chat = (props: ChatProps) => {
87
125
 
88
126
  return (
89
127
  <div className={className} style={style || {}}>
90
- <style>
91
- {`
92
- .copilotKitMessage.copilotKitAssistantMessage:has(.copilotKitMarkdown:empty) {
93
- display: none;
94
- }
95
- `}
96
- </style>
97
128
  {user_id && runtimeUrl && (
98
129
  <CopilotKit
99
- runtimeUrl={`${runtimeUrl}/copilotkit/chat`}
130
+ runtimeUrl={`${runtimeUrl}/copilotkit/chat${query}`}
100
131
  showDevConsole={false}
101
- headers={{ user_id: user_id || '', shopify_domain: shopify_domain }}
132
+ headers={{ user_id: user_id || '', shopify_domain: shopify_domain, account, locale }}
102
133
  >
103
134
  <CopilotPopup
104
135
  {...popup}
136
+ showResponseButton={showResponseButton !== 'follow'}
105
137
  labels={{
106
138
  title: title || 'DTC Live Chat',
107
139
  initial: start || '',
108
140
  }}
109
- makeSystemMessage={() => {
110
- return start || ''
141
+ instructions={start || ''}
142
+ onInProgress={load => {
143
+ if (openPopup && load) setCurrentSuggestions([])
144
+ if (openPopup && !load) getSuggestions()
111
145
  }}
146
+ Messages={({ messages, inProgress }) => (
147
+ <Messages
148
+ messages={messages}
149
+ inProgress={inProgress}
150
+ ResponseButton={showResponseButton === 'follow' ? <ResponseButton /> : undefined}
151
+ >
152
+ <Suggestions currentSuggestions={currentSuggestions} />
153
+ </Messages>
154
+ )}
112
155
  Button={({ open, setOpen }) => {
113
- const Button = popup?.Button
114
- useEffect(() => setOpenPopup(open), [open])
115
-
156
+ const Button = popup?.Button || DefaultButton
157
+ // eslint-disable-next-line react-hooks/rules-of-hooks
158
+ useEffect(() => {
159
+ setOpenPopup(open)
160
+ }, [open])
116
161
  return (
117
162
  <>
118
163
  {lang?.popupTip && openTip && !openPopup && (
@@ -129,53 +174,18 @@ const Chat = (props: ChatProps) => {
129
174
  </button>
130
175
  </div>
131
176
  )}
132
- {Button && <Button open={open} setOpen={setOpen} setOpenTip={setOpenTip} />}
133
- {!Button && (
134
- <button
135
- onClick={() => {
136
- setOpen(true)
137
- setOpenTip(false)
138
- }}
139
- className="copilotKitButton "
140
- aria-label="Open Chat"
141
- >
142
- <div className="copilotKitButtonIcon copilotKitButtonIconOpen">
143
- <svg
144
- xmlns="http://www.w3.org/2000/svg"
145
- viewBox="0 0 24 24"
146
- fill="currentColor"
147
- width="24"
148
- height="24"
149
- >
150
- <g transform="translate(24, 0) scale(-1, 1)">
151
- <path
152
- fillRule="evenodd"
153
- d="M5.337 21.718a6.707 6.707 0 01-.533-.074.75.75 0 01-.44-1.223 3.73 3.73 0 00.814-1.686c.023-.115-.022-.317-.254-.543C3.274 16.587 2.25 14.41 2.25 12c0-5.03 4.428-9 9.75-9s9.75 3.97 9.75 9c0 5.03-4.428 9-9.75 9-.833 0-1.643-.097-2.417-.279a6.721 6.721 0 01-4.246.997z"
154
- clipRule="evenodd"
155
- ></path>
156
- </g>
157
- </svg>
158
- </div>
159
- <div className="copilotKitButtonIcon copilotKitButtonIconClose">
160
- <svg
161
- xmlns="http://www.w3.org/2000/svg"
162
- fill="none"
163
- viewBox="0 0 24 24"
164
- strokeWidth="1.5"
165
- stroke="currentColor"
166
- width="24"
167
- height="24"
168
- >
169
- <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path>
170
- </svg>
171
- </div>
172
- </button>
173
- )}
177
+ <Button open={open} setOpen={setOpen} setOpenTip={setOpenTip} />
174
178
  </>
175
179
  )
176
180
  }}
177
181
  >
178
- <CopilotAction start={start} history={history} buynowRender={buynowRender} signupRender={signupRender} />
182
+ <CopilotAction
183
+ start={start}
184
+ history={history}
185
+ addtocartRender={addtocartRender}
186
+ productRender={productRender}
187
+ signupRender={signupRender}
188
+ />
179
189
  </CopilotPopup>
180
190
  </CopilotKit>
181
191
  )}
@@ -0,0 +1,36 @@
1
+ import { type FC, memo } from 'react'
2
+ import ReactMarkdown, { type Options, type Components } from 'react-markdown'
3
+ import remarkGfm from 'remark-gfm'
4
+ import remarkMath from 'remark-math'
5
+
6
+ const MemoizedReactMarkdown: FC<Options> = memo(
7
+ ReactMarkdown,
8
+ (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className
9
+ )
10
+
11
+ type MarkdownProps = {
12
+ content: string
13
+ }
14
+
15
+ export const Markdown = ({ content }: MarkdownProps) => {
16
+ return (
17
+ <div className="copilotKitMarkdown">
18
+ <MemoizedReactMarkdown components={components} remarkPlugins={[remarkGfm, remarkMath]}>
19
+ {content}
20
+ </MemoizedReactMarkdown>
21
+ </div>
22
+ )
23
+ }
24
+
25
+ const components: Components = {
26
+ p({ children }) {
27
+ return <p>{children}</p>
28
+ },
29
+ a({ children, ...props }) {
30
+ return (
31
+ <a style={{ color: 'blue', textDecoration: 'underline' }} {...props} target="_blank" rel="noopener noreferrer">
32
+ {children}
33
+ </a>
34
+ )
35
+ },
36
+ }
@@ -0,0 +1,167 @@
1
+ import React, { useEffect, useMemo } from 'react'
2
+ import type { MessagesProps } from './props.js'
3
+ import { useChatContext } from '@copilotkit/react-ui'
4
+ import { Markdown } from './markdown.js'
5
+ import { type RenderFunctionStatus, useCopilotContext } from '@copilotkit/react-core'
6
+ import {
7
+ MessageStatusCode,
8
+ ActionExecutionMessage,
9
+ Message,
10
+ ResultMessage,
11
+ TextMessage,
12
+ Role,
13
+ } from '@copilotkit/runtime-client-gql'
14
+
15
+ const Messages = ({ messages, inProgress, ResponseButton, children }: MessagesProps) => {
16
+ const { chatComponentsCache } = useCopilotContext()
17
+
18
+ const context = useChatContext()
19
+ const initialMessages = useMemo(() => makeInitialMessages(context.labels.initial), [context.labels.initial])
20
+ messages = [...initialMessages, ...messages]
21
+
22
+ const functionResults: Record<string, string> = {}
23
+
24
+ for (let i = 0; i < messages.length; i++) {
25
+ if (messages[i] instanceof ActionExecutionMessage) {
26
+ const id = messages[i].id
27
+ const resultMessage: ResultMessage | undefined = messages.find(
28
+ message => message instanceof ResultMessage && message.actionExecutionId === id
29
+ ) as ResultMessage | undefined
30
+
31
+ if (resultMessage) {
32
+ functionResults[id] = ResultMessage.decodeResult(resultMessage.result || '')
33
+ }
34
+ }
35
+ }
36
+
37
+ const messagesEndRef = React.useRef<HTMLDivElement>(null)
38
+
39
+ const scrollToBottom = () => {
40
+ if (messagesEndRef.current) {
41
+ messagesEndRef.current.scrollIntoView({
42
+ behavior: 'auto',
43
+ })
44
+ }
45
+ }
46
+
47
+ useEffect(() => {
48
+ scrollToBottom()
49
+ }, [messages])
50
+
51
+ return (
52
+ <div className="copilotKitMessages">
53
+ {messages.map((message, index) => {
54
+ const isCurrentMessage = index === messages.length - 1
55
+
56
+ if (message instanceof TextMessage && message.role === 'user') {
57
+ return (
58
+ <div key={index} className="copilotKitMessage copilotKitUserMessage">
59
+ {message.content}
60
+ </div>
61
+ )
62
+ } else if (message instanceof TextMessage && message.role == 'assistant') {
63
+ return (
64
+ <div key={index} className={`copilotKitMessage copilotKitAssistantMessage`}>
65
+ {isCurrentMessage && inProgress && !message.content ? (
66
+ context.icons.spinnerIcon
67
+ ) : (
68
+ <Markdown content={message.content} />
69
+ )}
70
+ </div>
71
+ )
72
+ } else if (message instanceof ActionExecutionMessage) {
73
+ if (chatComponentsCache.current !== null && chatComponentsCache.current[message.name]) {
74
+ const render = chatComponentsCache.current[message.name]
75
+ // render a static string
76
+ if (typeof render === 'string') {
77
+ // when render is static, we show it only when in progress
78
+ if (isCurrentMessage && inProgress) {
79
+ return (
80
+ <div key={index} className={`copilotKitMessage copilotKitAssistantMessage`}>
81
+ {context.icons.spinnerIcon} <span className="inProgressLabel">{render}</span>
82
+ </div>
83
+ )
84
+ }
85
+ // Done - silent by default to avoid a series of "done" messages
86
+ else {
87
+ return null
88
+ }
89
+ }
90
+ // render is a function
91
+ else {
92
+ const args = message.arguments
93
+
94
+ let status: RenderFunctionStatus = 'inProgress'
95
+
96
+ if (functionResults[message.id] !== undefined) {
97
+ status = 'complete'
98
+ } else if (message.status.code !== MessageStatusCode.Pending) {
99
+ status = 'executing'
100
+ }
101
+
102
+ const toRender = render({
103
+ status: status as any,
104
+ args,
105
+ result: functionResults[message.id],
106
+ })
107
+
108
+ // No result and complete: stay silent
109
+ if (!toRender && status === 'complete') {
110
+ return null
111
+ }
112
+
113
+ if (typeof toRender === 'string') {
114
+ return (
115
+ <div key={index} className={`copilotKitMessage copilotKitAssistantMessage`}>
116
+ {isCurrentMessage && inProgress && context.icons.spinnerIcon} {toRender}
117
+ </div>
118
+ )
119
+ } else {
120
+ return (
121
+ <div key={index} className="copilotKitCustomAssistantMessage">
122
+ {toRender}
123
+ </div>
124
+ )
125
+ }
126
+ }
127
+ }
128
+ // No render function found- show the default message
129
+ else if (!inProgress || !isCurrentMessage) {
130
+ // Done - silent by default to avoid a series of "done" messages
131
+ return null
132
+ } else {
133
+ // In progress
134
+ return (
135
+ <div key={index} className={`copilotKitMessage copilotKitAssistantMessage`}>
136
+ {context.icons.spinnerIcon}
137
+ </div>
138
+ )
139
+ }
140
+ }
141
+ })}
142
+ <div className="responseButtonBox">{ResponseButton}</div>
143
+ <footer ref={messagesEndRef}>{children}</footer>
144
+ </div>
145
+ )
146
+ }
147
+
148
+ function makeInitialMessages(initial?: string | string[]): Message[] {
149
+ let initialArray: string[] = []
150
+ if (initial) {
151
+ if (Array.isArray(initial)) {
152
+ initialArray.push(...initial)
153
+ } else {
154
+ initialArray.push(initial)
155
+ }
156
+ }
157
+
158
+ return initialArray.map(
159
+ message =>
160
+ new TextMessage({
161
+ role: Role.Assistant,
162
+ content: message,
163
+ })
164
+ )
165
+ }
166
+
167
+ export default Messages
@@ -0,0 +1,51 @@
1
+ import React from 'react'
2
+ import { Message } from '@copilotkit/runtime-client-gql'
3
+
4
+ export interface ButtonProps {
5
+ open: boolean
6
+ setOpen: (open: boolean) => void
7
+ setOpenTip: (open: boolean) => void
8
+ }
9
+
10
+ export interface WindowProps {
11
+ open: boolean
12
+ setOpen: (open: boolean) => void
13
+ clickOutsideToClose: boolean
14
+ hitEscapeToClose: boolean
15
+ shortcut: string
16
+ children?: React.ReactNode
17
+ }
18
+
19
+ export interface HeaderProps {
20
+ open: boolean
21
+ setOpen: (open: boolean) => void
22
+ }
23
+
24
+ export interface ChatSuggestions {
25
+ currentSuggestions: SuggestionsProps[]
26
+ }
27
+
28
+ export interface SuggestionsProps {
29
+ title: string
30
+ message: string
31
+ partial?: boolean
32
+ className?: string
33
+ }
34
+
35
+ export interface MessagesProps {
36
+ messages: Message[]
37
+ inProgress: boolean
38
+ ResponseButton?: React.ReactElement
39
+ children?: React.ReactNode
40
+ }
41
+
42
+ export interface InputProps {
43
+ inProgress: boolean
44
+ onSend: (text: string) => Promise<Message>
45
+ isVisible?: boolean
46
+ }
47
+
48
+ export interface ResponseButtonProps {
49
+ onClick: () => void
50
+ inProgress: boolean
51
+ }
@@ -0,0 +1,17 @@
1
+ import { useChatContext } from '@copilotkit/react-ui'
2
+ import { useCopilotChat } from '@copilotkit/react-core'
3
+
4
+ const ResponseButton = () => {
5
+ const context = useChatContext()
6
+
7
+ const { isLoading, reloadMessages, stopGeneration } = useCopilotChat()
8
+
9
+ return (
10
+ <button onClick={isLoading ? stopGeneration : reloadMessages} className="copilotKitResponseButton">
11
+ {isLoading ? context.labels.stopGenerating : context.labels.regenerateResponse}
12
+ <span>{isLoading ? context.icons.stopIcon : context.icons.regenerateIcon}</span>
13
+ </button>
14
+ )
15
+ }
16
+
17
+ export default ResponseButton
@@ -0,0 +1,34 @@
1
+ import { useCopilotChat } from '@copilotkit/react-core'
2
+ import { Role, TextMessage } from '@copilotkit/runtime-client-gql'
3
+
4
+ import type { SuggestionsProps, ChatSuggestions } from './props.js'
5
+
6
+ const Suggestions = ({ currentSuggestions }: ChatSuggestions) => {
7
+ const { appendMessage } = useCopilotChat()
8
+
9
+ return (
10
+ currentSuggestions?.length > 0 && (
11
+ <div className="suggestions">
12
+ {currentSuggestions.map(({ title, message, partial, className }: SuggestionsProps, index: number) => (
13
+ <button
14
+ key={message + index}
15
+ disabled={partial}
16
+ onClick={e => {
17
+ e.preventDefault()
18
+ const m = new TextMessage({
19
+ content: message,
20
+ role: Role.User,
21
+ })
22
+ appendMessage(m)
23
+ }}
24
+ className={className || 'suggestion'}
25
+ >
26
+ <span>{title}</span>
27
+ </button>
28
+ ))}
29
+ </div>
30
+ )
31
+ )
32
+ }
33
+
34
+ export default Suggestions
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import type { Meta, StoryObj } from '@storybook/react'
3
-
4
3
  import { Chat } from '../components/index.js'
4
+ import '../styles/chat.css'
5
5
 
6
6
  type Story = StoryObj<typeof meta>
7
7
 
@@ -35,8 +35,8 @@ export const Default: Story = {
35
35
  title: 'DTC Live Chat',
36
36
  runtimeUrl: 'https://beta-dtcapi.anker.com',
37
37
  shopify_domain: 'soundcoreusa.myshopify.com',
38
- user_id: 'ar_411',
39
- query: '',
40
- lang: { popupTip: 'Hi ! Welcome to soundcore Innovations live chat!' },
38
+ user_id: 'arno5',
39
+ showResponseButton: 'follow',
40
+ lang: { popupTip: 'Hi ! Welcome to soundcore Innovations live chat!', popupTipTimeout: [3000, 6000] },
41
41
  },
42
42
  }
@@ -71,3 +71,7 @@
71
71
  flex-direction: column;
72
72
  justify-content: flex-end;
73
73
  }
74
+
75
+ .copilotKitMessage.copilotKitAssistantMessage:has(.copilotKitMarkdown:empty) {
76
+ display: none;
77
+ }