@anker-in/campaign-ui 0.0.33-alpha1 → 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.
- package/dist/cjs/components/chat/Markdown.d.ts +5 -0
- package/dist/cjs/components/chat/Messages.d.ts +3 -0
- package/dist/cjs/components/chat/Response.d.ts +2 -0
- package/dist/cjs/components/chat/action.d.ts +7 -3
- package/dist/cjs/components/chat/action.js +1 -1
- package/dist/cjs/components/chat/action.js.map +3 -3
- package/dist/cjs/components/chat/button.d.ts +3 -0
- package/dist/cjs/components/chat/button.js +2 -0
- package/dist/cjs/components/chat/button.js.map +7 -0
- package/dist/cjs/components/chat/index.d.ts +17 -3
- package/dist/cjs/components/chat/index.js +1 -5
- package/dist/cjs/components/chat/index.js.map +3 -3
- package/dist/cjs/components/chat/markdown.js +2 -0
- package/dist/cjs/components/chat/markdown.js.map +7 -0
- package/dist/cjs/components/chat/marksdown.d.ts +5 -0
- package/dist/cjs/components/chat/message.d.ts +2 -0
- package/dist/cjs/components/chat/messages.js +2 -0
- package/dist/cjs/components/chat/messages.js.map +7 -0
- package/dist/cjs/components/chat/props.d.ts +43 -0
- package/dist/cjs/components/chat/props.js +2 -0
- package/dist/cjs/components/chat/props.js.map +7 -0
- package/dist/cjs/components/chat/response copy.d.ts +2 -0
- package/dist/cjs/components/chat/response.js +2 -0
- package/dist/cjs/components/chat/response.js.map +7 -0
- package/dist/cjs/components/chat/rresponse.d.ts +2 -0
- package/dist/cjs/components/chat/suggestions.d.ts +3 -0
- package/dist/cjs/components/chat/suggestions.js +2 -0
- package/dist/cjs/components/chat/suggestions.js.map +7 -0
- package/dist/cjs/stories/chat.stories.d.ts +1 -0
- package/dist/cjs/stories/chat.stories.js +1 -1
- package/dist/cjs/stories/chat.stories.js.map +3 -3
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/components/chat/Markdown.d.ts +5 -0
- package/dist/esm/components/chat/Messages.d.ts +3 -0
- package/dist/esm/components/chat/Response.d.ts +2 -0
- package/dist/esm/components/chat/action.d.ts +7 -3
- package/dist/esm/components/chat/action.js +1 -1
- package/dist/esm/components/chat/action.js.map +3 -3
- package/dist/esm/components/chat/button.d.ts +3 -0
- package/dist/esm/components/chat/button.js +2 -0
- package/dist/esm/components/chat/button.js.map +7 -0
- package/dist/esm/components/chat/index.d.ts +17 -3
- package/dist/esm/components/chat/index.js +1 -5
- package/dist/esm/components/chat/index.js.map +3 -3
- package/dist/esm/components/chat/markdown.js +2 -0
- package/dist/esm/components/chat/markdown.js.map +7 -0
- package/dist/esm/components/chat/marksdown.d.ts +5 -0
- package/dist/esm/components/chat/message.d.ts +2 -0
- package/dist/esm/components/chat/messages.js +2 -0
- package/dist/esm/components/chat/messages.js.map +7 -0
- package/dist/esm/components/chat/props.d.ts +43 -0
- package/dist/esm/components/chat/props.js +2 -0
- package/dist/esm/components/chat/props.js.map +7 -0
- package/dist/esm/components/chat/response copy.d.ts +2 -0
- package/dist/esm/components/chat/response.js +2 -0
- package/dist/esm/components/chat/response.js.map +7 -0
- package/dist/esm/components/chat/rresponse.d.ts +2 -0
- package/dist/esm/components/chat/suggestions.d.ts +3 -0
- package/dist/esm/components/chat/suggestions.js +2 -0
- package/dist/esm/components/chat/suggestions.js.map +7 -0
- package/dist/esm/stories/chat.stories.d.ts +1 -0
- package/dist/esm/stories/chat.stories.js +1 -1
- package/dist/esm/stories/chat.stories.js.map +2 -2
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/src/components/chat/action.tsx +33 -12
- package/src/components/chat/button.tsx +23 -0
- package/src/components/chat/index.tsx +78 -68
- package/src/components/chat/markdown.tsx +36 -0
- package/src/components/chat/messages.tsx +167 -0
- package/src/components/chat/props.ts +51 -0
- package/src/components/chat/response.tsx +17 -0
- package/src/components/chat/suggestions.tsx +34 -0
- package/src/stories/chat.stories.tsx +4 -4
- package/src/styles/css/messages.css +4 -0
- package/src/styles/css/suggestions.css +1 -0
- package/style.css +14 -708
- package/dist/cjs/components/chat/chatContext.js +0 -2
- package/dist/cjs/components/chat/chatContext.js.map +0 -7
- package/dist/cjs/components/theme.js +0 -2
- package/dist/cjs/components/theme.js.map +0 -7
- package/dist/esm/components/chat/chatContext.js +0 -2
- package/dist/esm/components/chat/chatContext.js.map +0 -7
- package/dist/esm/components/theme.js +0 -2
- package/dist/esm/components/theme.js.map +0 -7
- 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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
+
addtocartRender?: string | ((_props: any) => React.ReactElement)
|
|
18
|
+
productRender?: string | ((_props: any) => React.ReactElement)
|
|
19
|
+
signupRender?: string | ((_props: any) => React.ReactElement)
|
|
17
20
|
children?: React.ReactNode
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export const CopilotAction = ({
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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:
|
|
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
|
-
|
|
39
|
+
productRender?: string | ((_props: any) => React.ReactElement)
|
|
24
40
|
/** 订阅卡片,接受参数:
|
|
25
41
|
* 后端接口文档:https://anker-in.feishu.cn/wiki/DOYJwE9oxipWmYk072ncnJNznBb
|
|
26
42
|
*/
|
|
27
|
-
signupRender?: string | ((_props: any) =>
|
|
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
|
-
|
|
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
|
|
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
|
-
},
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: '
|
|
39
|
-
|
|
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
|
}
|