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

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 (86) 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 +8 -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 +28 -4
  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 +8 -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 +28 -4
  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 +58 -14
  67. package/src/components/chat/button.tsx +23 -0
  68. package/src/components/chat/index.tsx +93 -69
  69. package/src/components/chat/markdown.tsx +36 -0
  70. package/src/components/chat/messages.tsx +208 -0
  71. package/src/components/chat/props.ts +51 -0
  72. package/src/components/chat/response.tsx +19 -0
  73. package/src/components/chat/suggestions.tsx +34 -0
  74. package/src/stories/chat.stories.tsx +5 -6
  75. package/src/styles/css/messages.css +12 -0
  76. package/src/styles/css/response.css +0 -1
  77. package/src/styles/css/suggestions.css +1 -0
  78. package/dist/cjs/components/chat/chatContext.js +0 -2
  79. package/dist/cjs/components/chat/chatContext.js.map +0 -7
  80. package/dist/cjs/components/theme.js +0 -2
  81. package/dist/cjs/components/theme.js.map +0 -7
  82. package/dist/esm/components/chat/chatContext.js +0 -2
  83. package/dist/esm/components/chat/chatContext.js.map +0 -7
  84. package/dist/esm/components/theme.js +0 -2
  85. package/dist/esm/components/theme.js.map +0 -7
  86. package/src/components/chat/chatContext.tsx +0 -161
@@ -1,29 +1,50 @@
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
+ gotocheckoutRender?: string | ((_props: any) => React.ReactElement)
18
+ gotocartRender?: string | ((_props: any) => React.ReactElement)
19
+ addtocartRender?: string | ((_props: any) => React.ReactElement)
20
+ productRender?: string | ((_props: any) => React.ReactElement)
16
21
  signupRender?: string | ((_props: any) => React.ReactElement)
17
22
  children?: React.ReactNode
18
23
  }
19
24
 
20
- export const CopilotAction = ({ history, buynowRender, signupRender, children }: ActionProps) => {
25
+ export const CopilotAction = ({
26
+ start,
27
+ history,
28
+ gotocheckoutRender,
29
+ gotocartRender,
30
+ addtocartRender,
31
+ productRender,
32
+ signupRender,
33
+ children,
34
+ }: ActionProps) => {
21
35
  const { setMessages } = useCopilotChat()
22
36
 
23
37
  useEffect(() => {
24
- const originhistory = history?.filter(value => value?.content)
38
+ const originhistory = history?.filter(value => value?.content || value?.arguments)
25
39
  if (originhistory?.length > 0) {
26
40
  const content = originhistory?.map(value => {
41
+ if (value?.name && value?.arguments) {
42
+ return new ActionExecutionMessage({
43
+ name: value?.name,
44
+ arguments: value?.arguments,
45
+ scope: value?.scope,
46
+ })
47
+ }
27
48
  return new TextMessage({
28
49
  ...value,
29
50
  role: value?.role === 'user' ? Role.User : Role.Assistant,
@@ -33,18 +54,41 @@ export const CopilotAction = ({ history, buynowRender, signupRender, children }:
33
54
  }
34
55
  }, [history])
35
56
 
36
- useCopilotChatSuggestions(
37
- {
38
- instructions: 'Suggest the most relevant next actions.',
57
+ useCopilotAction({
58
+ name: 'go_to_checkout',
59
+ description: 'go to cart',
60
+ parameters: [{ sku: '', handle: '' }] as any,
61
+ render: gotocheckoutRender || (props => <div className="hidden">{JSON.stringify(props)}</div>),
62
+ handler: function () {
63
+ // console.log('buynow-props', props)
39
64
  },
40
- []
41
- )
65
+ })
66
+
67
+ useCopilotAction({
68
+ name: 'go_to_cart',
69
+ description: 'go to cart',
70
+ parameters: [{ sku: '', handle: '' }] as any,
71
+ render: gotocartRender || (props => <div className="hidden">{JSON.stringify(props)}</div>),
72
+ handler: function () {
73
+ // console.log('buynow-props', props)
74
+ },
75
+ })
76
+
77
+ useCopilotAction({
78
+ name: 'add_to_cart',
79
+ description: 'add to cart',
80
+ parameters: [{ sku: '', handle: '' }] as any,
81
+ render: addtocartRender || (props => <div className="hidden">{JSON.stringify(props)}</div>),
82
+ handler: function () {
83
+ // console.log('buynow-props', props)
84
+ },
85
+ })
42
86
 
43
87
  useCopilotAction({
44
- name: 'buynow',
45
- description: 'buy now',
88
+ name: 'show_product_card',
89
+ description: 'product card',
46
90
  parameters: [{ sku: '', handle: '' }] as any,
47
- render: buynowRender || (props => <div>{JSON.stringify(props)}</div>),
91
+ render: productRender || (props => <div className="hidden">{JSON.stringify(props)}</div>),
48
92
  handler: function () {
49
93
  // console.log('buynow-props', props)
50
94
  },
@@ -58,7 +102,7 @@ export const CopilotAction = ({ history, buynowRender, signupRender, children }:
58
102
  name: 'signup',
59
103
  },
60
104
  ] as any,
61
- render: signupRender || (props => <div>{JSON.stringify(props)}</div>),
105
+ render: signupRender || (props => <div className="hidden">{JSON.stringify(props)}</div>),
62
106
  handler: function () {
63
107
  // console.log('signup-props', props)
64
108
  },
@@ -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,43 @@ 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
20
- /** 产品卡片,接受参数: {"status":"complete","args":[{"sku":"A3936031","handle":"space-a40-a3936031"}],"result":""}
29
+ /** 'follow' or 'unfollow' | true or false
30
+ * follow: ResponseButton 会跟随在 messages 的后边
31
+ * unfollow: ResponseButton 在聊天框底部
32
+ */
33
+ showResponseButton?: string | boolean
34
+ /** 跳转 checkout action 卡片,接受参数: {"status":"complete","args":[{"sku":["A3936031"],"handle":"space-a40-a3936031"}],"result":""}
35
+ * 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
36
+ */
37
+ gotocheckoutRender?: string | ((_props: any) => React.ReactElement)
38
+ /** 打开购物车 action 卡片,接受参数: {"status":"complete","args":[{"sku":["A3936031"],"handle":"space-a40-a3936031"}],"result":""}
39
+ * 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
40
+ */
41
+ gotocartRender?: string | ((_props: any) => React.ReactElement)
42
+ /** 加购卡片,接受参数: {"status":"complete","args":[{"sku":["A3936031"],"handle":"space-a40-a3936031"}],"result":""}
21
43
  * 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
22
44
  */
23
- buynowRender?: string | ((_props: any) => React.ReactElement)
24
- /** 订阅卡片,接受参数:
45
+ addtocartRender?: string | ((_props: any) => React.ReactElement)
46
+ /** 产品卡片,接受参数: {"status":"complete","args":[{"sku":["A3936031"],"handle":"space-a40-a3936031"}],"result":""}
47
+ * 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
48
+ */
49
+ productRender?: string | ((_props: any) => React.ReactElement)
50
+ /** 订阅卡片,接受参数: {"status":"complete","args":[{"sku":["A3936031"],"handle":"space-a40-a3936031"}],"result":""}
25
51
  * 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
26
52
  */
27
53
  signupRender?: string | ((_props: any) => React.ReactElement)
28
54
  /** CopilotPopup 自定义参数,参考:https://docs.copilotkit.ai/reference/components/CopilotPopup */
29
55
  popup?: any
30
- /** 文案,{"popupTip": "Hi ! Welcome to soundcore Innovations live chat!"} */
56
+ /** 文案,{"popupTip": "Hi ! Welcome to soundcore Innovations live chat!", "popupTipTimeout": [3000, 6000]} */
31
57
  lang?: any
32
58
  /** 参考此文档:https://docs.copilotkit.ai/concepts/customize-look-and-feel */
33
59
  style?: any
@@ -41,38 +67,62 @@ const Chat = (props: ChatProps) => {
41
67
  popup,
42
68
  shopify_domain,
43
69
  user_id = '',
70
+ account = '',
71
+ locale = '',
44
72
  query = '',
45
- buynowRender,
73
+ showResponseButton,
74
+ gotocheckoutRender,
75
+ gotocartRender,
76
+ addtocartRender,
77
+ productRender,
46
78
  signupRender,
47
79
  style,
48
80
  className = '',
49
81
  lang,
50
82
  } = props
83
+ const [autoTipOpen, autoTipClose] = lang?.popupTipTimeout || [3000, 6000]
51
84
 
52
85
  const [openTip, setOpenTip] = useState(false)
53
86
  const [openPopup, setOpenPopup] = useState(false)
54
87
  const [start, setstart] = useState('')
88
+ const [currentSuggestions, setCurrentSuggestions] = useState([])
55
89
  const [history, sethistory] = useState<[{ content: string; role: string }] | []>([])
56
90
 
57
91
  const getStart = useCallback(async () => {
58
92
  const result = await fetcher({
59
- url: `${runtimeUrl}/copilotkit/start`,
93
+ url: `${runtimeUrl}/copilotkit/start${query}`,
60
94
  method: 'GET',
61
- headers: { user_id: user_id || '', shopify_domain: shopify_domain },
95
+ headers: { user_id: user_id || '', shopify_domain, account, locale },
62
96
  })
63
97
 
98
+ setCurrentSuggestions(result?.suggested_questions?.map((item: string) => ({ message: item, title: item })) || [])
64
99
  setstart(result?.opening_statement || '')
65
100
  sethistory(result?.history || [])
66
- }, [user_id])
101
+ }, [user_id, shopify_domain, account, locale])
102
+
103
+ const getSuggestions = useCallback(async () => {
104
+ const result = await fetcher({
105
+ url: `${runtimeUrl}/copilotkit/suggested_questions${query}`,
106
+ method: 'GET',
107
+ headers: { user_id: user_id || '', shopify_domain, account, locale },
108
+ })
109
+
110
+ setCurrentSuggestions(
111
+ result?.suggested_questions?.data?.map((item: string) => ({ message: item, title: item })) || []
112
+ )
113
+ }, [user_id, shopify_domain, account, locale])
67
114
 
68
115
  const setOpenTipFn = () => {
69
116
  if (!openTip) setOpenTip(true)
117
+ setTimeout(() => {
118
+ setOpenTip(false)
119
+ }, autoTipClose)
70
120
  }
71
121
 
72
122
  useEffect(() => {
73
123
  const timer = setTimeout(() => {
74
124
  setOpenTipFn()
75
- }, 3000)
125
+ }, autoTipOpen)
76
126
 
77
127
  return () => {
78
128
  clearTimeout(timer)
@@ -87,32 +137,39 @@ const Chat = (props: ChatProps) => {
87
137
 
88
138
  return (
89
139
  <div className={className} style={style || {}}>
90
- <style>
91
- {`
92
- .copilotKitMessage.copilotKitAssistantMessage:has(.copilotKitMarkdown:empty) {
93
- display: none;
94
- }
95
- `}
96
- </style>
97
140
  {user_id && runtimeUrl && (
98
141
  <CopilotKit
99
- runtimeUrl={`${runtimeUrl}/copilotkit/chat`}
142
+ runtimeUrl={`${runtimeUrl}/copilotkit/chat${query}`}
100
143
  showDevConsole={false}
101
- headers={{ user_id: user_id || '', shopify_domain: shopify_domain }}
144
+ headers={{ user_id: user_id || '', shopify_domain: shopify_domain, account, locale }}
102
145
  >
103
146
  <CopilotPopup
104
147
  {...popup}
148
+ showResponseButton={showResponseButton !== 'follow'}
105
149
  labels={{
106
150
  title: title || 'DTC Live Chat',
107
151
  initial: start || '',
108
152
  }}
109
- makeSystemMessage={() => {
110
- return start || ''
153
+ instructions={start || ''}
154
+ onInProgress={load => {
155
+ if (openPopup && load) setCurrentSuggestions([])
156
+ if (openPopup && !load) getSuggestions()
111
157
  }}
158
+ Messages={({ messages, inProgress }) => (
159
+ <Messages
160
+ messages={messages}
161
+ inProgress={inProgress}
162
+ ResponseButton={showResponseButton === 'follow' ? <ResponseButton /> : undefined}
163
+ >
164
+ <Suggestions currentSuggestions={currentSuggestions} />
165
+ </Messages>
166
+ )}
112
167
  Button={({ open, setOpen }) => {
113
- const Button = popup?.Button
114
- useEffect(() => setOpenPopup(open), [open])
115
-
168
+ const Button = popup?.Button || DefaultButton
169
+ // eslint-disable-next-line react-hooks/rules-of-hooks
170
+ useEffect(() => {
171
+ setOpenPopup(open)
172
+ }, [open])
116
173
  return (
117
174
  <>
118
175
  {lang?.popupTip && openTip && !openPopup && (
@@ -129,53 +186,20 @@ const Chat = (props: ChatProps) => {
129
186
  </button>
130
187
  </div>
131
188
  )}
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
- )}
189
+ <Button open={open} setOpen={setOpen} setOpenTip={setOpenTip} />
174
190
  </>
175
191
  )
176
192
  }}
177
193
  >
178
- <CopilotAction start={start} history={history} buynowRender={buynowRender} signupRender={signupRender} />
194
+ <CopilotAction
195
+ start={start}
196
+ history={history}
197
+ gotocartRender={gotocartRender}
198
+ gotocheckoutRender={gotocheckoutRender}
199
+ addtocartRender={addtocartRender}
200
+ productRender={productRender}
201
+ signupRender={signupRender}
202
+ />
179
203
  </CopilotPopup>
180
204
  </CopilotKit>
181
205
  )}
@@ -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,208 @@
1
+ import React, { useEffect, useMemo, useState } 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 [isExpanded, setIsExpanded] = useState(true)
17
+
18
+ const { chatComponentsCache } = useCopilotContext()
19
+
20
+ const context = useChatContext()
21
+ const initialMessages = useMemo(() => makeInitialMessages(context.labels.initial), [context.labels.initial])
22
+ messages = [...initialMessages, ...messages]
23
+
24
+ const functionResults: Record<string, string> = {}
25
+
26
+ for (let i = 0; i < messages.length; i++) {
27
+ if (messages[i] instanceof ActionExecutionMessage) {
28
+ const id = messages[i].id
29
+ const resultMessage: ResultMessage | undefined = messages.find(
30
+ message => message instanceof ResultMessage && message.actionExecutionId === id
31
+ ) as ResultMessage | undefined
32
+
33
+ if (resultMessage) {
34
+ functionResults[id] = ResultMessage.decodeResult(resultMessage.result || '')
35
+ }
36
+ }
37
+ }
38
+
39
+ const messagesEndRef = React.useRef<HTMLDivElement>(null)
40
+ const messagesEndChildRef = React.useRef<HTMLDivElement>(null)
41
+
42
+ const scrollToBottom = () => {
43
+ if (messagesEndRef.current) {
44
+ messagesEndRef.current.scrollIntoView({
45
+ behavior: 'auto',
46
+ })
47
+ }
48
+ }
49
+
50
+ useEffect(() => {
51
+ scrollToBottom()
52
+ }, [messages])
53
+
54
+ useEffect(() => {
55
+ const textarea = document.querySelector('.copilotKitInput textarea') as HTMLTextAreaElement
56
+
57
+ textarea &&
58
+ textarea.addEventListener('click', function () {
59
+ if (textarea.value && document.activeElement === textarea) {
60
+ setIsExpanded(false)
61
+ } else {
62
+ setIsExpanded(true)
63
+ scrollToBottom()
64
+ }
65
+ })
66
+ textarea &&
67
+ textarea.addEventListener('input', function () {
68
+ if (textarea.value && document.activeElement === textarea) {
69
+ setIsExpanded(false)
70
+ } else {
71
+ setIsExpanded(true)
72
+ scrollToBottom()
73
+ }
74
+ })
75
+ }, [])
76
+
77
+ useEffect(() => {
78
+ const content = messagesEndChildRef.current
79
+ if (isExpanded && content) {
80
+ content.style.maxHeight = '500px'
81
+ content.style.overflow = 'visible'
82
+ } else if (!isExpanded && content) {
83
+ content.style.maxHeight = '0'
84
+ content.style.overflow = 'hidden'
85
+ }
86
+ }, [isExpanded])
87
+
88
+ return (
89
+ <div className="copilotKitMessages">
90
+ {messages.map((message, index) => {
91
+ const isCurrentMessage = index === messages.length - 1
92
+
93
+ if (message instanceof TextMessage && message.role === 'user') {
94
+ return (
95
+ <div key={index} className="copilotKitMessage copilotKitUserMessage">
96
+ {message.content}
97
+ </div>
98
+ )
99
+ } else if (message instanceof TextMessage && message.role == 'assistant') {
100
+ return (
101
+ <div key={index} className={`copilotKitMessage copilotKitAssistantMessage`}>
102
+ {isCurrentMessage && inProgress && !message.content ? (
103
+ context.icons.spinnerIcon
104
+ ) : (
105
+ <Markdown content={message.content} />
106
+ )}
107
+ </div>
108
+ )
109
+ } else if (message instanceof ActionExecutionMessage) {
110
+ if (chatComponentsCache.current !== null && chatComponentsCache.current[message.name]) {
111
+ const render = chatComponentsCache.current[message.name]
112
+ // render a static string
113
+ if (typeof render === 'string') {
114
+ // when render is static, we show it only when in progress
115
+ if (isCurrentMessage && inProgress) {
116
+ return (
117
+ <div key={index} className={`copilotKitMessage copilotKitAssistantMessage`}>
118
+ {context.icons.spinnerIcon} <span className="inProgressLabel">{render}</span>
119
+ </div>
120
+ )
121
+ }
122
+ // Done - silent by default to avoid a series of "done" messages
123
+ else {
124
+ return null
125
+ }
126
+ }
127
+ // render is a function
128
+ else {
129
+ const args = message.arguments
130
+
131
+ let status: RenderFunctionStatus = 'inProgress'
132
+
133
+ if (functionResults[message.id] !== undefined) {
134
+ status = 'complete'
135
+ } else if (message.status.code !== MessageStatusCode.Pending) {
136
+ status = 'executing'
137
+ }
138
+
139
+ const toRender = render({
140
+ status: status as any,
141
+ args,
142
+ result: functionResults[message.id],
143
+ })
144
+
145
+ // No result and complete: stay silent
146
+ if (!toRender && status === 'complete') {
147
+ return null
148
+ }
149
+
150
+ if (typeof toRender === 'string') {
151
+ return (
152
+ <div key={index} className={`copilotKitMessage copilotKitAssistantMessage`}>
153
+ {isCurrentMessage && inProgress && context.icons.spinnerIcon} {toRender}
154
+ </div>
155
+ )
156
+ } else {
157
+ return (
158
+ <div key={index} className="copilotKitCustomAssistantMessage">
159
+ {toRender}
160
+ </div>
161
+ )
162
+ }
163
+ }
164
+ }
165
+ // No render function found- show the default message
166
+ else if (!inProgress || !isCurrentMessage) {
167
+ // Done - silent by default to avoid a series of "done" messages
168
+ return null
169
+ } else {
170
+ // In progress
171
+ return (
172
+ <div key={index} className={`copilotKitMessage copilotKitAssistantMessage`}>
173
+ {context.icons.spinnerIcon}
174
+ </div>
175
+ )
176
+ }
177
+ }
178
+ })}
179
+ <div className="responseButtonBox">{ResponseButton}</div>
180
+ <footer ref={messagesEndRef}>
181
+ <div className="copilotKitMessagesFooter" ref={messagesEndChildRef}>
182
+ {children}
183
+ </div>
184
+ </footer>
185
+ </div>
186
+ )
187
+ }
188
+
189
+ function makeInitialMessages(initial?: string | string[]): Message[] {
190
+ let initialArray: string[] = []
191
+ if (initial) {
192
+ if (Array.isArray(initial)) {
193
+ initialArray.push(...initial)
194
+ } else {
195
+ initialArray.push(initial)
196
+ }
197
+ }
198
+
199
+ return initialArray.map(
200
+ message =>
201
+ new TextMessage({
202
+ role: Role.Assistant,
203
+ content: message,
204
+ })
205
+ )
206
+ }
207
+
208
+ 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
+ }