@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.
- package/package.json +27 -0
- package/src/agents/app.ts +173 -0
- package/src/agents/common.ts +111 -0
- package/src/agents/index.ts +7 -0
- package/src/components/icons/index.less +8 -0
- package/src/components/icons/index.tsx +24 -0
- package/src/components/messages/index.less +806 -0
- package/src/components/messages/index.tsx +236 -0
- package/src/context/index.ts +21 -0
- package/src/data.ts +5 -0
- package/src/index.tsx +84 -0
- package/src/mock.ts +1267 -0
- package/src/startView/index.less +216 -0
- package/src/startView/index.tsx +229 -0
- package/src/tools/analyze-and-expand-prd.ts +166 -0
- package/src/tools/answer-user.ts +35 -0
- package/src/tools/focus-element.ts +47 -0
- package/src/tools/generate-page.ts +750 -0
- package/src/tools/get-component-info-by-ids.ts +166 -0
- package/src/tools/get-component-info.ts +53 -0
- package/src/tools/get-components-doc-and-prd.ts +137 -0
- package/src/tools/get-focus-mybricks-dsl.ts +26 -0
- package/src/tools/get-mybricks-dsl.ts +73 -0
- package/src/tools/index.ts +25 -0
- package/src/tools/modify-component.ts +385 -0
- package/src/tools/type.d.ts +12 -0
- package/src/tools/utils.ts +62 -0
- package/src/types.d.ts +65 -0
- package/src/view/components/header/header.less +17 -0
- package/src/view/components/header/header.tsx +15 -0
- package/src/view/components/index.ts +3 -0
- package/src/view/components/messages/messages.less +228 -0
- package/src/view/components/messages/messages.tsx +172 -0
- package/src/view/components/sender/sender.less +44 -0
- package/src/view/components/sender/sender.tsx +62 -0
- package/src/view/index.less +5 -0
- package/src/view/index.tsx +18 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
.editor {
|
|
2
|
+
z-index: 13;
|
|
3
|
+
position: relative;
|
|
4
|
+
background-color: #ffffff;
|
|
5
|
+
border: solid 1px rgba(0, 0, 0, .13);
|
|
6
|
+
max-width: 768px;
|
|
7
|
+
width: 100%;
|
|
8
|
+
border-radius: 20px;
|
|
9
|
+
box-shadow: 0 5px 16px -4px #00000012;
|
|
10
|
+
color: rgba(0, 0, 0, .9);
|
|
11
|
+
cursor: default;
|
|
12
|
+
|
|
13
|
+
.topArea {
|
|
14
|
+
padding: 12px;
|
|
15
|
+
display: flex;
|
|
16
|
+
gap: 8px;
|
|
17
|
+
align-items: center;
|
|
18
|
+
box-shadow: 0 .5px 0 0 rgba(0, 0, 0, .13);
|
|
19
|
+
|
|
20
|
+
.imageThumbnail {
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
pointer-events: auto;
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
width: 56px;
|
|
25
|
+
aspect-ratio: 1;
|
|
26
|
+
min-width: 56px;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
border: 1px solid rgba(0, 0, 0, .13);
|
|
29
|
+
position: relative;
|
|
30
|
+
|
|
31
|
+
img {
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 100%;
|
|
34
|
+
display: inline-block;
|
|
35
|
+
background-repeat: no-repeat;
|
|
36
|
+
background-size: cover;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.imageDeleteContainer {
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
position: absolute;
|
|
43
|
+
top: 0;
|
|
44
|
+
right: 0;
|
|
45
|
+
padding: 4px;
|
|
46
|
+
z-index: 2;
|
|
47
|
+
|
|
48
|
+
.imageDeleteIcon {
|
|
49
|
+
width: 12px;
|
|
50
|
+
height: 12px;
|
|
51
|
+
border-radius: 50%;
|
|
52
|
+
background-color: rgba(0, 0, 0, .4);
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
color: #ffffff;
|
|
57
|
+
|
|
58
|
+
svg {
|
|
59
|
+
width: 8px;
|
|
60
|
+
height: 8px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&:hover {
|
|
64
|
+
opacity: 0.8;
|
|
65
|
+
background-color: var(--mybricks-color-primary);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.input {
|
|
73
|
+
padding: 12px 16px 10px;
|
|
74
|
+
line-height: 20px;
|
|
75
|
+
min-height: 60px;
|
|
76
|
+
position: relative;
|
|
77
|
+
|
|
78
|
+
.inputEditorContainer {
|
|
79
|
+
font-size: 12px;
|
|
80
|
+
max-height: 140px;
|
|
81
|
+
scrollbar-width: none;
|
|
82
|
+
position: relative;
|
|
83
|
+
min-height: inherit;
|
|
84
|
+
overflow: auto;
|
|
85
|
+
|
|
86
|
+
.inputEditor {
|
|
87
|
+
user-select: text;
|
|
88
|
+
white-space: pre-wrap;
|
|
89
|
+
word-break: break-word;
|
|
90
|
+
width: 100%;
|
|
91
|
+
max-height: fit-content;
|
|
92
|
+
min-height: inherit;
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
outline: none;
|
|
95
|
+
cursor: text;
|
|
96
|
+
|
|
97
|
+
&[contentEditable="false"] {
|
|
98
|
+
cursor: default;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.inputPlaceholder {
|
|
103
|
+
position: absolute;
|
|
104
|
+
overflow: hidden;
|
|
105
|
+
text-overflow: ellipsis;
|
|
106
|
+
top: 1px;
|
|
107
|
+
left: 1px;
|
|
108
|
+
user-select: none;
|
|
109
|
+
pointer-events: none;
|
|
110
|
+
color: rgba(0, 0, 0, .6);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.editorAction {
|
|
116
|
+
display: flex;
|
|
117
|
+
justify-content: space-between;
|
|
118
|
+
align-items: flex-end;
|
|
119
|
+
padding: 0 8px;
|
|
120
|
+
margin-top: 2px;
|
|
121
|
+
margin-bottom: 8px;
|
|
122
|
+
position: relative;
|
|
123
|
+
|
|
124
|
+
.leftArea {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
|
|
128
|
+
.attachmentButton {
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
width: 32px;
|
|
134
|
+
border-radius: 20px;
|
|
135
|
+
height: 32px;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
|
|
138
|
+
svg {
|
|
139
|
+
width: 20px;
|
|
140
|
+
height: 20px;
|
|
141
|
+
color: rgba(0, 0, 0, .6);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&:hover {
|
|
145
|
+
background-color: rgba(0, 0, 0, .09);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
input {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.rightArea {
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
|
|
158
|
+
.sendButtonContainer {
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
background-color: var(--mybricks-color-primary);
|
|
161
|
+
border-radius: 12px;
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
color: rgba(255, 255, 255, .9);
|
|
165
|
+
|
|
166
|
+
.sendButton {
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
justify-content: center;
|
|
171
|
+
width: 32px;
|
|
172
|
+
height: 32px;
|
|
173
|
+
|
|
174
|
+
svg {
|
|
175
|
+
width: 28px;
|
|
176
|
+
height: 28px;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.loadingButton {
|
|
181
|
+
svg {
|
|
182
|
+
width: 14px;
|
|
183
|
+
height: 14px;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.disabled {
|
|
189
|
+
cursor: default;
|
|
190
|
+
opacity: 0.5;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
:global {
|
|
196
|
+
* {
|
|
197
|
+
scrollbar-color: transparent transparent;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.temp {
|
|
203
|
+
z-index: 13;
|
|
204
|
+
position: relative;
|
|
205
|
+
background-color: #ffffff;
|
|
206
|
+
border: solid 1px rgba(0, 0, 0, .13);
|
|
207
|
+
max-width: 768px;
|
|
208
|
+
width: 100%;
|
|
209
|
+
border-radius: 20px;
|
|
210
|
+
box-shadow: 0 5px 16px -4px #00000012;
|
|
211
|
+
color: rgba(0, 0, 0, .9);
|
|
212
|
+
cursor: default;
|
|
213
|
+
height: 80%;
|
|
214
|
+
padding: 10px;
|
|
215
|
+
display: flex;
|
|
216
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react"
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import { message } from "antd";
|
|
4
|
+
import { ArrowUp, Attachment, Loading, Close } from "../components/icons";
|
|
5
|
+
import { Agents } from "../agents";
|
|
6
|
+
// import { Messages } from "../view/components/messages/messages";
|
|
7
|
+
import { Messages } from "../components/messages";
|
|
8
|
+
import css from "./index.less"
|
|
9
|
+
import { context } from "../context";
|
|
10
|
+
|
|
11
|
+
const readFileToBase64 = (file: File): Promise<string> => {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const reader = new FileReader();
|
|
14
|
+
reader.onload = function (event) {
|
|
15
|
+
if (event.target) {
|
|
16
|
+
const base64 = event.target.result as string;
|
|
17
|
+
resolve(base64);
|
|
18
|
+
} else {
|
|
19
|
+
reject(event);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
reader.onerror = function (event) {
|
|
23
|
+
reject(event);
|
|
24
|
+
};
|
|
25
|
+
reader.readAsDataURL(file);
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const StartView = ({ user, copilot }: any) => {
|
|
30
|
+
const inputEditorRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
const [isComposing, setIsComposing] = useState(false);
|
|
32
|
+
const [inputContent, setInputContent] = useState<string | null>(null);
|
|
33
|
+
const [loading, setLoading] = useState(false);
|
|
34
|
+
const [attachments, setAttachments] = useState<string[]>([]);
|
|
35
|
+
|
|
36
|
+
const send = () => {
|
|
37
|
+
const inputContent = inputEditorRef.current!.textContent || "开发一个商城APP";
|
|
38
|
+
if (inputContent && !loading) {
|
|
39
|
+
setLoading(true);
|
|
40
|
+
|
|
41
|
+
Agents.requestGenerateCanvasAgent({
|
|
42
|
+
message: inputContent,
|
|
43
|
+
attachments: attachments.map((attachment) => {
|
|
44
|
+
return {
|
|
45
|
+
type: "image",
|
|
46
|
+
content: attachment
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
}).then(() => {
|
|
50
|
+
|
|
51
|
+
}).catch((e) => {
|
|
52
|
+
message.error(e)
|
|
53
|
+
}).finally(() => {
|
|
54
|
+
setLoading(false);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const onInput = () => {
|
|
60
|
+
setInputContent(inputEditorRef.current!.textContent)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
64
|
+
if (event.key === "Enter") {
|
|
65
|
+
if (isComposing) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (event.shiftKey) {
|
|
70
|
+
|
|
71
|
+
} else {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
send();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const onCompositionStart = () => {
|
|
79
|
+
setIsComposing(true);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const onCompositionEnd = () => {
|
|
83
|
+
setIsComposing(false);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const uploadAttachment = () => {
|
|
87
|
+
const fileInput = document.createElement('input');
|
|
88
|
+
fileInput.type = 'file';
|
|
89
|
+
fileInput.accept = 'image/*';
|
|
90
|
+
|
|
91
|
+
fileInput.addEventListener('change', function (e) {
|
|
92
|
+
const target = e.target as HTMLInputElement;
|
|
93
|
+
if (!target) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const file = target.files?.[0];
|
|
97
|
+
|
|
98
|
+
if (file) {
|
|
99
|
+
if (!file.type.startsWith('image/')) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
readFileToBase64(file)
|
|
104
|
+
.then((base64) => {
|
|
105
|
+
setAttachments((attachments) => {
|
|
106
|
+
return [...attachments, base64]
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
.catch((event) => {
|
|
110
|
+
console.error("[@mybricks/plugin-ai - 上传附件失败]", event);
|
|
111
|
+
message.error("[@mybricks/plugin-ai - 上传附件失败]");
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
fileInput.click();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const deleteAttachment = (index: number) => {
|
|
120
|
+
setAttachments((attachments) => {
|
|
121
|
+
attachments.splice(index, 1)
|
|
122
|
+
return [...attachments]
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const onPaste = (event: React.ClipboardEvent<HTMLDivElement>) => {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
const file = event.clipboardData.files[0];
|
|
129
|
+
if (file?.type.startsWith('image/')) {
|
|
130
|
+
readFileToBase64(file)
|
|
131
|
+
.then((base64) => {
|
|
132
|
+
setAttachments((attachments) => {
|
|
133
|
+
return [...attachments, base64]
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
.catch((event) => {
|
|
137
|
+
console.error("[@mybricks/plugin-ai - 上传附件失败]", event);
|
|
138
|
+
message.error("[@mybricks/plugin-ai - 上传附件失败]");
|
|
139
|
+
})
|
|
140
|
+
} else {
|
|
141
|
+
const content = event.clipboardData.getData('text/plain');
|
|
142
|
+
|
|
143
|
+
if (!content) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const selection = window.getSelection();
|
|
148
|
+
|
|
149
|
+
if (!selection?.rangeCount) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const range = selection.getRangeAt(0);
|
|
154
|
+
range.deleteContents();
|
|
155
|
+
const textNode = document.createTextNode(content);
|
|
156
|
+
range.insertNode(textNode);
|
|
157
|
+
range.setStartAfter(textNode);
|
|
158
|
+
range.setEndAfter(textNode);
|
|
159
|
+
|
|
160
|
+
setInputContent(inputEditorRef.current!.textContent)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
inputEditorRef.current!.focus();
|
|
166
|
+
}, [])
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
{/* TODO:临时,把Messages再封装下 */}
|
|
171
|
+
<div className={css.temp} style={{ display: loading ? "flex" : "none"}}>
|
|
172
|
+
<Messages user={user} rxai={context.rxai} copilot={copilot}/>
|
|
173
|
+
</div>
|
|
174
|
+
<div className={css.editor} style={{ display: !loading ? "block" : "none"}}>
|
|
175
|
+
{attachments.length ? <div className={css.topArea}>
|
|
176
|
+
{attachments.map((attachment, index) => {
|
|
177
|
+
return (
|
|
178
|
+
<div className={css.imageThumbnail}>
|
|
179
|
+
<img src={attachment} />
|
|
180
|
+
<div className={css.imageDeleteContainer} onClick={() => deleteAttachment(index)}>
|
|
181
|
+
<div className={css.imageDeleteIcon}>
|
|
182
|
+
<Close />
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
})}
|
|
188
|
+
</div> : null}
|
|
189
|
+
<div className={css.input}>
|
|
190
|
+
<div className={css.inputEditorContainer}>
|
|
191
|
+
<div
|
|
192
|
+
ref={inputEditorRef}
|
|
193
|
+
className={css.inputEditor}
|
|
194
|
+
contentEditable={!loading}
|
|
195
|
+
onKeyDown={onKeyDown}
|
|
196
|
+
onCompositionStart={onCompositionStart}
|
|
197
|
+
onCompositionEnd={onCompositionEnd}
|
|
198
|
+
onInput={onInput}
|
|
199
|
+
onPaste={onPaste}
|
|
200
|
+
></div>
|
|
201
|
+
{!inputContent && <div className={css.inputPlaceholder}>
|
|
202
|
+
您好,我是智能助手,请详细描述您要搭建的应用内容
|
|
203
|
+
</div>}
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div className={css.editorAction}>
|
|
207
|
+
<div className={css.leftArea}>
|
|
208
|
+
<div className={css.attachmentButton} onClick={uploadAttachment}>
|
|
209
|
+
<Attachment />
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
<div className={css.rightArea}>
|
|
213
|
+
<div className={classNames(css.sendButtonContainer, {
|
|
214
|
+
[css.disabled]: !inputContent || loading
|
|
215
|
+
})} onClick={send}>
|
|
216
|
+
<div className={classNames(css.sendButton, {
|
|
217
|
+
[css.loadingButton]: loading
|
|
218
|
+
})}>
|
|
219
|
+
{loading ? <Loading /> : <ArrowUp />}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { StartView }
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { fileFormat } from '@mybricks/rxai'
|
|
2
|
+
import { getFiles } from './utils'
|
|
3
|
+
|
|
4
|
+
interface AnalyzeAndExpandPrdParams {
|
|
5
|
+
onProjectCreate: (projectJson: any) => void,
|
|
6
|
+
demo?: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function analyzeAndExpandPrd(config: AnalyzeAndExpandPrdParams): any {
|
|
10
|
+
const {
|
|
11
|
+
demo = {
|
|
12
|
+
title: '本地生活APP',
|
|
13
|
+
pages: [
|
|
14
|
+
{
|
|
15
|
+
title: '首页',
|
|
16
|
+
prd: `目的:诱导用户进行点击,完成商品转化
|
|
17
|
+
需求:
|
|
18
|
+
从上往下依次为
|
|
19
|
+
1. 搜索模块:提供全局的商品搜索能力
|
|
20
|
+
2. 导航入口:一般使用一行N列来提供各个子界面的快捷入口
|
|
21
|
+
3. 轮播图:轮播当前热门的活动图片
|
|
22
|
+
4. 猜你喜欢:使用商品瀑布流来展示各类商品`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
title: '首页',
|
|
26
|
+
prd: `目的:对所有商品提供分类索引入口
|
|
27
|
+
需求:
|
|
28
|
+
从上往下依次为
|
|
29
|
+
1. 搜索模块:提供全局的商品搜索能力
|
|
30
|
+
2. 分类模块:
|
|
31
|
+
2.1 分类侧栏:用于提供商品分类的快捷入口
|
|
32
|
+
2.2 商品列表:在分类侧栏的子项里展示当前分类的商品列表`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: '我的',
|
|
36
|
+
prd: `目的:提供对个人信息的查看以及修改界面
|
|
37
|
+
需求:
|
|
38
|
+
从上往下依次为
|
|
39
|
+
1. 个人信息:通常包含头像和昵称,以及二维码信息
|
|
40
|
+
2. 订单入口:提供对订单已支付、已收获等分类的快捷入口
|
|
41
|
+
3. 会员信息:提供各类会员优惠活动入口`,
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
style: `一个偏向活力的橙色风格是一个不错的选择
|
|
45
|
+
- 颜色
|
|
46
|
+
- 主颜色:活力橙 #FF5733
|
|
47
|
+
- 背景色:浅灰色 #F7F7F7
|
|
48
|
+
- 文本颜色:沉稳黑 #000000 可用于主要的文本
|
|
49
|
+
- 二级颜色:灰色 #A6A6A6 可用于边框或者浅色文本
|
|
50
|
+
- 样式
|
|
51
|
+
- 圆角:12px,增加柔和感
|
|
52
|
+
- 间距:16px
|
|
53
|
+
同样的,你也可以参考一些常见的APP,比如美团和饿了么的元素设计。`
|
|
54
|
+
}
|
|
55
|
+
} = config ?? {}
|
|
56
|
+
return {
|
|
57
|
+
name: 'analyze-and-expand-prd',
|
|
58
|
+
displayName: "对原始产品需求文档分析并扩写",
|
|
59
|
+
description: `对用户提供的原始产品需求文档进行需求分析与扩写,最后拆分成不同的页面,输出结构化的结果。
|
|
60
|
+
参数:原始需求文档(可能是一句话、一个图片、甚至各种类型附件)
|
|
61
|
+
作用:将模糊、简略的需求转化为清晰、分页面的详细描述,用于指导后续低代码搭建;
|
|
62
|
+
返回值:扩写后的结构化文档;`,
|
|
63
|
+
getPrompts: () => {
|
|
64
|
+
return `<工具总览>
|
|
65
|
+
你是MyBricks低代码平台(以下简称MyBricks平台或MyBricks)的资深搭建助手,经验丰富、实事求是、逻辑严谨。
|
|
66
|
+
|
|
67
|
+
你需要根据用户提出的问题或需求,切换不同的身份,完成以下任务
|
|
68
|
+
|
|
69
|
+
任务一:根据【用户需求】,作为产品经理,为用户合理整理或者拓展需求,返回整理好的需求;
|
|
70
|
+
任务二:根据【用户需求】,作为产品经理,为用户提供一个应用的标题,言简意赅,不多于10个字;
|
|
71
|
+
任务三:根据【用户需求】和【拓展需求】,作为设计师,为用户提供样式设计参考;
|
|
72
|
+
</工具总览>
|
|
73
|
+
|
|
74
|
+
<特别注意>
|
|
75
|
+
- 对话可能由多轮构成,每轮对话中,用户会提出不同的问题或给与信息补充,你需要根据用户的问题、逐步分析处理。
|
|
76
|
+
- 你所面向的用户是MyBricks平台上的用户,这些用户不是专业的开发人员,因此你需要以简洁、易懂的方式,回答用户的问题。
|
|
77
|
+
- 如果附件中有图片,请在设计开发中作为重要参考,进行详细的需求及设计分析,当作用户的需求。
|
|
78
|
+
</特别注意>
|
|
79
|
+
|
|
80
|
+
<遵循原则>
|
|
81
|
+
你要切换不同的角色来完成一个需求的设计和开发,同时特别注意,生成的页面数量不得超过5个。
|
|
82
|
+
</遵循原则>
|
|
83
|
+
|
|
84
|
+
<处理流程>
|
|
85
|
+
根据你的角色定义来完成以下任务,汇总给出一个Json
|
|
86
|
+
<任务一>
|
|
87
|
+
角色:产品经理
|
|
88
|
+
工作任务:梳理 / 拓展需求,并且分析总共需要几个页面来承接这个系统
|
|
89
|
+
返回内容:每个页面的名称以及需求
|
|
90
|
+
对应字段:页面字段中的title以及prd
|
|
91
|
+
返回规则:
|
|
92
|
+
- 如果是一句话需求,从上到下梳理并拓展需求
|
|
93
|
+
- 如何定义一句话需求?例如 "一个简历页面" "实现用户管理系统" "一个首页"
|
|
94
|
+
- 如何拓展?
|
|
95
|
+
- "一个简历页面" -> "一个个人简历页面,包含个人介绍、技能特长、项目经历、联系方式"
|
|
96
|
+
- "实现用户管理系统" -> "
|
|
97
|
+
一个用户管理的系统,包含用户管理页面
|
|
98
|
+
- 查询列表:查询、新增、删除用户
|
|
99
|
+
- 新增用户页面
|
|
100
|
+
- 删除用户页面"
|
|
101
|
+
- 如果需求较为详实,则整理即可,不要拓展需求
|
|
102
|
+
- 如何定义需求较为详实?例如 "一个包含导航、公司介绍、公司优势、页脚的公司官网",这种对内容有定义的需求就无需拓展
|
|
103
|
+
- 如果用户提供了图片,将用户提供的【用户需求】原样返回即可
|
|
104
|
+
注意:
|
|
105
|
+
- 由于我们不能实现太复杂的需求,需要控制拓展需求的规模,拓展不要超过5个页面
|
|
106
|
+
- 需求仅围绕UI的实现来拓展,不要涉及多语言、服务端、逻辑、SEO、打印、截图、动画,以及一些复杂交互的周边能力,我们仅关注UI部分
|
|
107
|
+
</任务一>
|
|
108
|
+
|
|
109
|
+
<任务二>
|
|
110
|
+
角色:产品经理
|
|
111
|
+
返回内容:总结的应用标题,言简意赅,不超过10个字
|
|
112
|
+
对应字段:title
|
|
113
|
+
</任务二>
|
|
114
|
+
|
|
115
|
+
<任务三>
|
|
116
|
+
角色:设计师
|
|
117
|
+
返回内容:给出设计规范的建议,范围局限于颜色和样式,和参考建议。不提供任何需求和布局方面的信息,风格化信息是给到下一轮大模型的提示词,提供建议即可。
|
|
118
|
+
对应字段:style
|
|
119
|
+
返回规则:
|
|
120
|
+
- 区分场景:
|
|
121
|
+
- 对于C端网页,可以多考虑几个区域的风格样式建议,以及一些常见APP的风格推荐
|
|
122
|
+
- 对于中后台系统,默认使用antd风格,仅提供一些重点区域的风格样式建议
|
|
123
|
+
- 范围:目前限制在颜色、字体样式(不包含字体)、阴影和圆角这些基础UI细节,不要考虑视差滚动这类复杂样式
|
|
124
|
+
- 不要提供针对组件维度的样式建议,要从需求维度去建议
|
|
125
|
+
- 如果用户自己提了风格化主题相关需求,整理扩展即可
|
|
126
|
+
</任务三>
|
|
127
|
+
</处理流程>
|
|
128
|
+
|
|
129
|
+
<examples>
|
|
130
|
+
<example>
|
|
131
|
+
<user_query>一个本地生活APP</user_query>
|
|
132
|
+
<assistant_response>
|
|
133
|
+
好的,即将为你生成一个关于一个本地生活APP的页面。
|
|
134
|
+
|
|
135
|
+
这是我的思考结果:
|
|
136
|
+
由于当前信息较少,我们来扩写下需求,一个本地生活APP,一般包含「首页」、「分类页」、「个人页」等界面。
|
|
137
|
+
|
|
138
|
+
从需求来看,我觉得可以给应用起「本地生活APP」这个标题。
|
|
139
|
+
|
|
140
|
+
同时作为我也会给出我的组件建议和设计规范。
|
|
141
|
+
|
|
142
|
+
最终我们应该返回这样的结构
|
|
143
|
+
${fileFormat({
|
|
144
|
+
content: JSON.stringify(demo, null, 2),
|
|
145
|
+
fileName: '本地生活APP项目需求文档.json'
|
|
146
|
+
})}
|
|
147
|
+
</assistant_response>
|
|
148
|
+
</example>
|
|
149
|
+
</examples>`;
|
|
150
|
+
},
|
|
151
|
+
// aiRole: 'architect',
|
|
152
|
+
aiRole: "expert",
|
|
153
|
+
execute({ files, content }) {
|
|
154
|
+
const projectFile = getFiles(files, { extName: 'json' });
|
|
155
|
+
let projectJson = {}
|
|
156
|
+
try {
|
|
157
|
+
projectJson = JSON.parse(projectFile?.content)
|
|
158
|
+
} catch (error) {
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
config.onProjectCreate(projectJson)
|
|
162
|
+
return content;
|
|
163
|
+
},
|
|
164
|
+
streamThoughts: true
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { fileFormat } from '@mybricks/rxai'
|
|
2
|
+
|
|
3
|
+
interface AnswerUserParams {
|
|
4
|
+
// 暂无配置参数,未来可扩展语气风格、知识库范围等
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default function answerUser(config: AnswerUserParams): any {
|
|
8
|
+
return {
|
|
9
|
+
name: 'answer-user',
|
|
10
|
+
displayName: "专家思考中",
|
|
11
|
+
description: `回答用户在页面搭建过程中/对话中的问题。
|
|
12
|
+
参数:用户提出的问题(文本),前面工具仅获取一些小白难以理解的内容;
|
|
13
|
+
作用:理解用户意图,针对搭建专用术语和前面工具的复杂返回,为用户提供清晰、准确、与 MyBricks 平台相关的解答或引导;
|
|
14
|
+
返回值:一段自然语言回答;
|
|
15
|
+
|
|
16
|
+
建议使用场景:
|
|
17
|
+
1. 前置只有一个获取DSL工具,返回的是MyBricksDsl
|
|
18
|
+
2. 前置只有一个获取组件配置文档,返回的是专业的组件知识
|
|
19
|
+
`,
|
|
20
|
+
getPrompts: () => {
|
|
21
|
+
return `<工具总览>
|
|
22
|
+
你是一个 MyBricks 低代码平台的智能问答助手,专注于解答用户在页面/组件搭建过程中提出的各类问题。
|
|
23
|
+
你需要根据历史对话记录,为用户解答疑难问题。
|
|
24
|
+
</工具总览>
|
|
25
|
+
|
|
26
|
+
<人设>
|
|
27
|
+
你是一个能将复杂问题通俗易懂进行解释的MyBricks 平台专家,擅长用简单易懂的语言帮助用户理解问题。
|
|
28
|
+
</人设>
|
|
29
|
+
`;
|
|
30
|
+
},
|
|
31
|
+
execute({ content }) {
|
|
32
|
+
return content;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { fileFormat } from '@mybricks/rxai'
|
|
2
|
+
|
|
3
|
+
interface FocusElementToolParams {
|
|
4
|
+
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default function focusElement(config: FocusElementToolParams, ): Tool {
|
|
8
|
+
return {
|
|
9
|
+
name: 'focus-element',
|
|
10
|
+
description: `通过id聚焦到任何可聚焦的页面 / 组件。
|
|
11
|
+
参数:页面ID或者组件ID;
|
|
12
|
+
作用:聚焦到某个可搭建的元素上,获取元素元信息,用于后续搭建;
|
|
13
|
+
返回值:聚焦元素的元信息,格式是包含id、type(component / page)、namespace(可能存在)字段的对象;
|
|
14
|
+
`,
|
|
15
|
+
getPrompts: () => {
|
|
16
|
+
return `<工具总览>
|
|
17
|
+
你是一个可以聚焦页面/组件的工具,你作为MyBricks低代码平台(以下简称MyBricks平台或MyBricks)的资深页面搭建助手,可以根据需求聚焦元素用于下一步的搭建。
|
|
18
|
+
</工具总览>
|
|
19
|
+
|
|
20
|
+
<任务流程>
|
|
21
|
+
按照以下格式返回所要聚焦的元素:
|
|
22
|
+
${fileFormat({ content: '{ "id": "u_1sd23" }', fileName: '聚焦元素ID.json' })}
|
|
23
|
+
- 注意:文件要严格按照JSON格式返回,注意不要出现语法错误;
|
|
24
|
+
</任务流程>`
|
|
25
|
+
},
|
|
26
|
+
execute({ files, content }) {
|
|
27
|
+
let selectFile: File | undefined = undefined;
|
|
28
|
+
Object.keys(files).forEach((fileKey) => {
|
|
29
|
+
const file: File = files[fileKey] as File;
|
|
30
|
+
if (file.extension === 'json') {
|
|
31
|
+
selectFile = file
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
let selectFileId = {};
|
|
37
|
+
try {
|
|
38
|
+
selectFileId = JSON.parse(selectFile?.content);
|
|
39
|
+
} catch (error) {}
|
|
40
|
+
|
|
41
|
+
return `<当前选中元素情况>
|
|
42
|
+
ID: ${selectFileId.id}
|
|
43
|
+
类型:页面
|
|
44
|
+
</当前选中元素情况>`
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|