@mybricks/plugin-ai 0.0.1

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 (37) hide show
  1. package/package.json +27 -0
  2. package/src/agents/app.ts +173 -0
  3. package/src/agents/common.ts +111 -0
  4. package/src/agents/index.ts +7 -0
  5. package/src/components/icons/index.less +8 -0
  6. package/src/components/icons/index.tsx +24 -0
  7. package/src/components/messages/index.less +806 -0
  8. package/src/components/messages/index.tsx +236 -0
  9. package/src/context/index.ts +21 -0
  10. package/src/data.ts +5 -0
  11. package/src/index.tsx +84 -0
  12. package/src/mock.ts +1267 -0
  13. package/src/startView/index.less +216 -0
  14. package/src/startView/index.tsx +229 -0
  15. package/src/tools/analyze-and-expand-prd.ts +166 -0
  16. package/src/tools/answer-user.ts +35 -0
  17. package/src/tools/focus-element.ts +47 -0
  18. package/src/tools/generate-page.ts +750 -0
  19. package/src/tools/get-component-info-by-ids.ts +166 -0
  20. package/src/tools/get-component-info.ts +53 -0
  21. package/src/tools/get-components-doc-and-prd.ts +137 -0
  22. package/src/tools/get-focus-mybricks-dsl.ts +26 -0
  23. package/src/tools/get-mybricks-dsl.ts +73 -0
  24. package/src/tools/index.ts +25 -0
  25. package/src/tools/modify-component.ts +385 -0
  26. package/src/tools/type.d.ts +12 -0
  27. package/src/tools/utils.ts +62 -0
  28. package/src/types.d.ts +65 -0
  29. package/src/view/components/header/header.less +17 -0
  30. package/src/view/components/header/header.tsx +15 -0
  31. package/src/view/components/index.ts +3 -0
  32. package/src/view/components/messages/messages.less +228 -0
  33. package/src/view/components/messages/messages.tsx +172 -0
  34. package/src/view/components/sender/sender.less +44 -0
  35. package/src/view/components/sender/sender.tsx +62 -0
  36. package/src/view/index.less +5 -0
  37. package/src/view/index.tsx +18 -0
@@ -0,0 +1,228 @@
1
+ .messages {
2
+ width: 100%;
3
+ flex: 1;
4
+ overflow-y: auto;
5
+ display: flex;
6
+ flex-direction: column;
7
+ overflow-anchor: auto;
8
+ /* 允许锚点定位,浏览器默认行为 */
9
+ scroll-snap-type: y proximity;
10
+ gap: 16px;
11
+ padding-bottom: 30px;
12
+
13
+ .message {
14
+ width: 100%;
15
+
16
+ .bubble {
17
+ max-width: calc(100% - 30px);
18
+ width: fit-content;
19
+ background-color: rgba(0, 0, 0, 0.06);
20
+ padding: 6px;
21
+ border-radius: 8px;
22
+ word-break: break-all;
23
+
24
+ p:last-child {
25
+ margin-bottom: 0;
26
+ }
27
+
28
+ code {
29
+ text-wrap: auto;
30
+ }
31
+
32
+ .spinIcon {
33
+ vertical-align: -0.5px;
34
+ margin-left: 3px;
35
+ font-size: 10px;
36
+ animation: spin 1.2s linear infinite;
37
+ }
38
+
39
+ @keyframes spin {
40
+ from {
41
+ transform: rotate(0deg);
42
+ }
43
+
44
+ to {
45
+ transform: rotate(360deg);
46
+ }
47
+ }
48
+ }
49
+
50
+ .plan {
51
+ width: 100%;
52
+
53
+ div {
54
+ white-space: nowrap;
55
+ overflow: hidden;
56
+ text-overflow: ellipsis;
57
+ }
58
+ }
59
+ }
60
+
61
+ .messgaeEnd {
62
+ display: flex;
63
+ justify-content: flex-end;
64
+ }
65
+
66
+ .anchor {
67
+ /* 放在列表最后的空节点 */
68
+ height: 1px;
69
+ /* 越小越好,仅作锚点 */
70
+ scroll-snap-align: end;
71
+ /* 关键:永远吸底 */
72
+ }
73
+ }
74
+
75
+ .plan {
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: space-between;
79
+ background-color: #f3f3f3;
80
+ border-radius: 4px;
81
+ }
82
+
83
+ .bubbleContainer {
84
+ display: flex;
85
+ flex-direction: column;
86
+ color: #333;
87
+ border-radius: 8px;
88
+ border: 1px solid transparent;
89
+ padding: 16px 16px 0;
90
+
91
+ .bubbleHeader {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: start;
95
+ gap: 8px;
96
+ padding-bottom: 8px;
97
+
98
+ &:has(.userAvatar) {
99
+ justify-content: end;
100
+ flex-direction: row-reverse;
101
+ }
102
+
103
+ .bubbleHeaderAvatar {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ border-radius: 50%;
108
+ width: 20px;
109
+ height: 20px;
110
+ box-sizing: content-box;
111
+ border: 2px solid #ffffff;
112
+
113
+ .userAvatar {
114
+ width: 20px;
115
+ height: 20px;
116
+ border-radius: 50%;
117
+ }
118
+
119
+ .aiAvatar {
120
+ width: 24px;
121
+ height: 24px;
122
+ border-radius: 50%;
123
+ }
124
+ }
125
+
126
+ .bubbleHeaderName {
127
+ font-size: 12px;
128
+ color: #999;
129
+ }
130
+ }
131
+ }
132
+
133
+ .messageContainer {
134
+ user-select: text;
135
+ width: 100%;
136
+ // font-size: 14px;
137
+ font-size: 12px;
138
+ border-radius: 12px;
139
+ padding: 0;
140
+
141
+ p:last-child {
142
+ margin-bottom: 0;
143
+ }
144
+ }
145
+
146
+ .userMessage {
147
+ width: fit-content;
148
+ max-width: calc(100% - 16px);
149
+ align-self: flex-end;
150
+ align-items: end;
151
+ background: #ecf4ff;
152
+ border-radius: 4px;
153
+ padding: 8px;
154
+ border-color: transparent;
155
+ }
156
+
157
+ .markDown {
158
+ color: #333;
159
+ line-height: calc(1em + 8px);
160
+ word-break: break-word;
161
+
162
+ code {
163
+ text-wrap: auto;
164
+ }
165
+
166
+ .spinIcon {
167
+ vertical-align: -0.5px;
168
+ margin-left: 3px;
169
+ font-size: 10px;
170
+ animation: spin 1.2s linear infinite;
171
+ }
172
+
173
+ @keyframes spin {
174
+ from {
175
+ transform: rotate(0deg);
176
+ }
177
+
178
+ to {
179
+ transform: rotate(360deg);
180
+ }
181
+ }
182
+
183
+ p:last-child {
184
+ margin-bottom: 0;
185
+ }
186
+ }
187
+
188
+ .collapsibleCodeBlock {
189
+ background-color: #f3f3f3;
190
+ border-radius: 4px;
191
+ margin: 10px 0;
192
+ margin-top: 2px;
193
+ padding: 6px;
194
+ transition: all .4s cubic-bezier(.25, .8, .25, 1);
195
+ will-change: padding, height, transform;
196
+ transform-origin: top;
197
+ padding: 4px 8px;
198
+ transform: translateY(-1px);
199
+
200
+ .codeHeader {
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: space-between;
204
+ user-select: none;
205
+ border-radius: 4px;
206
+ min-height: 22px;
207
+ padding-bottom: 8px;
208
+ box-sizing: content-box;
209
+ transition: padding .4s cubic-bezier(.25, .8, .25, 1);
210
+ padding-bottom: 0;
211
+
212
+ .codeTitle {
213
+ margin: 0;
214
+ display: flex;
215
+ align-items: center;
216
+ gap: 8px;
217
+ color: currentColor;
218
+ font-weight: 400;
219
+ }
220
+
221
+ .codeTitleStatus {
222
+ display: inline-flex;
223
+ align-items: center;
224
+ justify-content: center;
225
+ font-size: 12px;
226
+ }
227
+ }
228
+ }
@@ -0,0 +1,172 @@
1
+ import React, { useEffect, useLayoutEffect, useState, useRef } from "react"
2
+ import classNames from "classnames"
3
+ import { Loading3QuartersOutlined, CheckCircleOutlined, CloseCircleOutlined } from "@ant-design/icons"
4
+ import { marked } from "marked";
5
+ import { context } from "../../../context"
6
+ import css from "./messages.less"
7
+
8
+ const Messages = ({ user }: any) => {
9
+ const destroysRef = useRef<(() => void)[]>([]);
10
+ const [plans, setPlans] = useState<any>([]);
11
+
12
+ useLayoutEffect(() => {
13
+ // @ts-ignore TODO
14
+ destroysRef.current.push(context.rxai.events.on('plan', (plans: any) => {
15
+ setPlans([...plans])
16
+ }, true))
17
+ }, [])
18
+
19
+ useEffect(() => {
20
+ return () => {
21
+ for (const destroy of destroysRef.current) {
22
+ destroy()
23
+ }
24
+ }
25
+ }, [])
26
+
27
+ return (
28
+ <div className={classNames(css.messages)}>
29
+ {plans.map((plan: any, index: number) => {
30
+ return <Plan key={index} plan={plan} user={user} />
31
+ })}
32
+ <div className={classNames(css.anchor)} />
33
+ </div>
34
+ )
35
+ }
36
+
37
+ export { Messages }
38
+
39
+ const Plan = ({ plan, user }: { plan: any, user: any }) => {
40
+ const [messages, setMessages] = useState<any[]>([])
41
+ const [message, setMessage] = useState("")
42
+ const [loading, setLoading] = useState(true)
43
+ const destroysRef = useRef<any[]>([]);
44
+ const messageRef = useRef<HTMLDivElement>(null)
45
+
46
+ useLayoutEffect(() => {
47
+ destroysRef.current.push(
48
+ plan.events.on('loading', (loading: boolean) => {
49
+ setLoading(loading);
50
+ }, true),
51
+ plan.events.on('messageStream', (messageStream: string) => {
52
+ setMessage((pre) => {
53
+ return pre + messageStream;
54
+ })
55
+ }, true),
56
+ plan.events.on('userFriendlyMessages', (messages: any[]) => {
57
+ setMessages([...messages])
58
+ setMessage("")
59
+ }, true),
60
+ )
61
+ }, [])
62
+
63
+ useEffect(() => {
64
+ return () => {
65
+ for (const destroy of destroysRef.current) {
66
+ destroy()
67
+ }
68
+ }
69
+ }, [])
70
+
71
+ useEffect(() => {
72
+ if (message) {
73
+ messageRef.current!.innerHTML = marked.parse(message) as string;
74
+ }
75
+ }, [message])
76
+
77
+ return (
78
+ <>
79
+ {messages[0] && (
80
+ <div className={css.bubbleContainer}>
81
+ <div className={css.bubbleHeader}>
82
+ <span className={css.bubbleHeaderAvatar}>
83
+ <img className={css.userAvatar} src={user.avatar} />
84
+ </span>
85
+ <span className={css.bubbleHeaderName}>{user.name}</span>
86
+ </div>
87
+ <BubbleUser message={messages[0]} />
88
+ </div>
89
+ )}
90
+ {messages[0] && (
91
+ <div className={css.bubbleContainer}>
92
+ <div className={css.bubbleHeader}>
93
+ <span className={css.bubbleHeaderAvatar}>
94
+ <img className={css.aiAvatar} src={"https://my.mybricks.world/image/icon.png"} />
95
+ </span>
96
+ <span className={css.bubbleHeaderName}>MyBricks.ai</span>
97
+ </div>
98
+ <div className={classNames(css.messageContainer)}>
99
+ <div className={css.markDown}>
100
+ {messages.slice(1).map((message, index) => {
101
+ return message.role === "tool" ? <BubbleAITool key={index + message.status} message={message} /> : <BubbleAI key={index} message={message} />
102
+ })}
103
+ {message ? (
104
+ <div ref={messageRef}>
105
+ </div>
106
+ ) : null}
107
+ {!message && loading ? (
108
+ <>
109
+ <span>正在思考</span>
110
+ <Loading3QuartersOutlined className={css.spinIcon} />
111
+ </>
112
+ ) : null}
113
+ </div>
114
+ </div>
115
+ </div>
116
+ )}
117
+ </>
118
+ )
119
+ }
120
+
121
+ const BubbleAITool = ({ message }: any) => {
122
+ return (
123
+ <div className={css.collapsibleCodeBlock}>
124
+ <div className={css.codeHeader}>
125
+ <span className={css.codeTitle}>{message.content.displayName}</span>
126
+ <span className={css.codeTitleStatus}>
127
+ {message.status === "pending" && <Loading3QuartersOutlined className={css.spinIcon} />}
128
+ {message.status === "success" && <CheckCircleOutlined style={{ color: "green" }} />}
129
+ {message.status === "error" && <CloseCircleOutlined style={{ color: "red" }} />}
130
+ </span>
131
+ </div>
132
+ </div>
133
+ )
134
+ }
135
+
136
+ const BubbleAI = ({ message }: any) => {
137
+ const bubbleRef = useRef<HTMLDivElement>(null);
138
+
139
+ useLayoutEffect(() => {
140
+ const content = typeof message.content === "string" ?
141
+ message.content :
142
+ message.content.find((content: any) => {
143
+ if (content.type === "text") {
144
+ return content
145
+ }
146
+ })?.text
147
+ bubbleRef.current!.innerHTML = marked.parse(content) as string;
148
+ }, [])
149
+
150
+ return <span ref={bubbleRef}></span>
151
+ }
152
+
153
+ const BubbleUser = ({ message }: any) => {
154
+ const bubbleRef = useRef<HTMLDivElement>(null);
155
+
156
+ useLayoutEffect(() => {
157
+ const content = typeof message.content === "string" ?
158
+ message.content :
159
+ message.content.find((content: any) => {
160
+ if (content.type === "text") {
161
+ return content
162
+ }
163
+ })?.text
164
+ bubbleRef.current!.innerHTML = marked.parse(content) as string;
165
+ }, [])
166
+
167
+ return (
168
+ <div className={classNames(css.messageContainer, css.userMessage)}>
169
+ <div ref={bubbleRef} className={css.markDown}></div>
170
+ </div>
171
+ )
172
+ }
@@ -0,0 +1,44 @@
1
+ .sender {
2
+ width: 100%;
3
+ padding: 12px;
4
+
5
+ .content {
6
+ width: 100%;
7
+ height: 40px;
8
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02), 0 2px 4px 0 rgba(0, 0, 0, 0.02);
9
+ border-radius: 12px;
10
+ border: 1px solid #d9d9d9;
11
+ display: flex;
12
+ align-items: center;
13
+ padding: 6px;
14
+
15
+ .textarea {
16
+ resize: none;
17
+ flex: 1;
18
+ font-size: 12px;
19
+ height: 22px;
20
+ line-height: 22px;
21
+ }
22
+
23
+ .button {
24
+ width: 28px;
25
+ height: 28px;
26
+ border-radius: 50%;
27
+ background-color: var(--mybricks-color-primary);
28
+
29
+ &:hover {
30
+ cursor: pointer;
31
+ }
32
+
33
+ &:disabled {
34
+ opacity: 0.5;
35
+ cursor: not-allowed;
36
+ }
37
+
38
+ .icon {
39
+ font-size: 14px;
40
+ color: #ffffff;
41
+ }
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,62 @@
1
+ import React, { useState } from "react"
2
+ import classNames from "classnames"
3
+ import { ArrowUpOutlined } from "@ant-design/icons"
4
+ import { context } from "../../../context"
5
+ import { Agents } from '../../../agents'
6
+ import css from "./sender.less"
7
+
8
+ const Sender = () => {
9
+ const [message, setMessage] = useState('');
10
+
11
+ const send = () => {
12
+ Agents.requestCommonAgent({
13
+ message
14
+ })
15
+
16
+ // context.rxai.requestAI({
17
+ // message,
18
+ // emits: {
19
+ // write: () => { },
20
+ // complete: () => { },
21
+ // error: () => { },
22
+ // cancel: () => { }
23
+ // },
24
+ // key: "",
25
+ // tools: context.getTools(),
26
+ // presetMessages: context.getPresetMessages()
27
+ // })
28
+ setMessage("");
29
+ }
30
+
31
+ return (
32
+ <div className={classNames(css.sender)}>
33
+ <div className={classNames(css.content)}>
34
+ <textarea
35
+ className={classNames(css.textarea)}
36
+ value={message}
37
+ onChange={(event) => {
38
+ setMessage(event.target.value);
39
+ }}
40
+ autoFocus
41
+ onKeyDown={(event) => {
42
+ if (event.key === 'Enter' && !event.shiftKey) {
43
+ event.preventDefault()
44
+ if (message) {
45
+ send()
46
+ }
47
+ }
48
+ }}
49
+ />
50
+ <button
51
+ className={classNames(css.button)}
52
+ disabled={!message}
53
+ onClick={send}
54
+ >
55
+ <ArrowUpOutlined className={classNames(css.icon)} />
56
+ </button>
57
+ </div>
58
+ </div>
59
+ )
60
+ }
61
+
62
+ export { Sender }
@@ -0,0 +1,5 @@
1
+ .view {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react"
2
+ import classNames from "classnames";
3
+ import { Header, Sender } from "./components";
4
+ import { Messages } from "../components/messages";
5
+ import { context } from "../context";
6
+ import css from "./index.less";
7
+
8
+ const View = ({ user, copilot }: any) => {
9
+ return (
10
+ <div className={classNames(css.view)}>
11
+ <Header />
12
+ <Messages user={user} copilot={copilot} rxai={context.rxai}/>
13
+ <Sender />
14
+ </div>
15
+ )
16
+ }
17
+
18
+ export { View }