@mybricks/plugin-ai 0.0.1 → 0.0.2
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 +7 -2
- package/src/agents/app.ts +188 -60
- package/src/agents/common.ts +428 -68
- package/src/agents/custom.ts +14 -0
- package/src/agents/index.ts +31 -1
- package/src/agents/knowledges/README.md +614 -0
- package/src/agents/knowledges/SUMMARY.md +527 -0
- package/src/agents/knowledges/index.ts +8 -0
- package/src/agents/knowledges/knowledge-base.ts +565 -0
- package/src/agents/knowledges/knowledge-node.ts +266 -0
- package/src/agents/knowledges/types.ts +208 -0
- package/src/agents/utils/config.ts +427 -0
- package/src/agents/workspace/coding-manager.ts +31 -0
- package/src/agents/workspace/components-manager.ts +124 -0
- package/src/agents/workspace/outline-focus.ts +188 -0
- package/src/agents/workspace/outline-info.ts +520 -0
- package/src/agents/workspace/page-tree-generator.ts +83 -0
- package/src/agents/workspace/workspace.ts +319 -0
- package/src/agents/workspace-by-knowledges/MIGRATION.md +568 -0
- package/src/agents/workspace-by-knowledges/README.md +521 -0
- package/src/agents/workspace-by-knowledges/index.ts +11 -0
- package/src/agents/workspace-by-knowledges/providers/component-docs-provider.ts +92 -0
- package/src/agents/workspace-by-knowledges/providers/focus-info-provider.ts +131 -0
- package/src/agents/workspace-by-knowledges/providers/index.ts +8 -0
- package/src/agents/workspace-by-knowledges/providers/project-info-provider.ts +151 -0
- package/src/agents/workspace-by-knowledges/test.ts +240 -0
- package/src/agents/workspace-by-knowledges/types.ts +56 -0
- package/src/agents/workspace-by-knowledges/utils/components-manager.ts +145 -0
- package/src/agents/workspace-by-knowledges/utils/index.ts +8 -0
- package/src/agents/workspace-by-knowledges/utils/outline-focus.ts +178 -0
- package/src/agents/workspace-by-knowledges/utils/outline-info.ts +521 -0
- package/src/agents/workspace-by-knowledges/workspace.ts +166 -0
- package/src/api/cloud-components.ts +129 -0
- package/src/api-record-replay/README.md +187 -0
- package/src/api-record-replay/index.ts +11 -0
- package/src/api-record-replay/manager.ts +168 -0
- package/src/api-record-replay/recorder.ts +117 -0
- package/src/api-record-replay/replayer.ts +148 -0
- package/src/components/attachments/index.less +117 -0
- package/src/components/attachments/index.tsx +136 -0
- package/src/components/icons/index.tsx +21 -1
- package/src/components/index.less +34 -0
- package/src/components/mention/index.less +23 -0
- package/src/components/mention/index.tsx +19 -0
- package/src/components/messages/index.less +444 -237
- package/src/components/messages/index.tsx +371 -88
- package/src/components/sender/index.less +203 -0
- package/src/components/sender/index.tsx +298 -0
- package/src/components/types.ts +31 -0
- package/src/constants/index.ts +8 -0
- package/src/context/RequestStatusTracker.ts +50 -0
- package/src/context/index.ts +68 -6
- package/src/{types.d.ts → global.d.ts} +40 -5
- package/src/index.tsx +212 -32
- package/src/preset/agents.ts +380 -0
- package/src/preset/createTemplates.ts +25 -0
- package/src/preset/index.ts +12 -0
- package/src/preset/prompts.ts +235 -0
- package/src/preset/requestAsStream.ts +246 -0
- package/src/preset/user.ts +6 -0
- package/src/startView/components/header/header.less +17 -0
- package/src/startView/components/header/header.tsx +15 -0
- package/src/startView/components/index.ts +1 -0
- package/src/startView/index.less +22 -204
- package/src/startView/index.tsx +35 -203
- package/src/tools/analyze-and-expand-prd.ts +192 -86
- package/src/tools/analyze-requirement-and-components.ts +589 -0
- package/src/tools/answer.ts +59 -0
- package/src/tools/build-process.ts +1174 -0
- package/src/tools/coding-subagent-as-tool.ts +119 -0
- package/src/tools/generate-ui-content.ts +1083 -0
- package/src/tools/index.ts +22 -19
- package/src/tools/open-dsl.ts +69 -0
- package/src/tools/refactor-ui-content.ts +801 -0
- package/src/tools/utils.ts +880 -28
- package/src/types/index.ts +4 -0
- package/src/view/components/header/header.less +36 -2
- package/src/view/components/header/header.tsx +47 -2
- package/src/view/components/index.ts +0 -2
- package/src/view/index.tsx +158 -8
- package/src/tools/answer-user.ts +0 -35
- package/src/tools/focus-element.ts +0 -47
- package/src/tools/generate-page.ts +0 -750
- package/src/tools/get-component-info-by-ids.ts +0 -166
- package/src/tools/get-component-info.ts +0 -53
- package/src/tools/get-components-doc-and-prd.ts +0 -137
- package/src/tools/get-focus-mybricks-dsl.ts +0 -26
- package/src/tools/get-mybricks-dsl.ts +0 -73
- package/src/tools/modify-component.ts +0 -385
- package/src/view/components/messages/messages.less +0 -228
- package/src/view/components/messages/messages.tsx +0 -172
- package/src/view/components/sender/sender.less +0 -44
- package/src/view/components/sender/sender.tsx +0 -62
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import React, { useRef, useState, useEffect, useLayoutEffect } from "react"
|
|
2
2
|
import classNames from "classnames"
|
|
3
3
|
import { Rxai } from "@mybricks/rxai"
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import markdownit from 'markdown-it'
|
|
5
|
+
import { Extension } from "../types";
|
|
6
|
+
import { Loading, Success, Chat } from "../icons";
|
|
7
|
+
import { AttachmentsList } from "../attachments";
|
|
8
|
+
import { MentionTag } from "../mention";
|
|
9
|
+
import { Mention } from "../types";
|
|
10
|
+
import { Sender, SenderRef, SenderProps } from "../sender";
|
|
11
|
+
import { context } from "../../context";
|
|
6
12
|
import css from "./index.less"
|
|
7
13
|
|
|
8
|
-
const
|
|
9
|
-
renderer.paragraph = (paragraph) => {
|
|
10
|
-
return `<p>${paragraph.text}</p>`;
|
|
11
|
-
};
|
|
12
|
-
marked.use({ renderer });
|
|
14
|
+
const md = markdownit()
|
|
13
15
|
|
|
14
16
|
interface User {
|
|
15
17
|
/** 名称 */
|
|
@@ -25,24 +27,65 @@ interface MessagesParams {
|
|
|
25
27
|
copilot: User;
|
|
26
28
|
|
|
27
29
|
rxai: Rxai;
|
|
30
|
+
|
|
31
|
+
onMentionClick?: (mention: Mention) => void;
|
|
32
|
+
|
|
33
|
+
onSend?: SenderProps['onSend'];
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
type Plans = Rxai['cacheMessages'];
|
|
31
37
|
|
|
32
38
|
const Messages = (params: MessagesParams) => {
|
|
33
|
-
const { user, rxai, copilot } = params;
|
|
39
|
+
const { user, rxai, copilot, onSend, onMentionClick } = params;
|
|
34
40
|
|
|
41
|
+
const mainRef = useRef<HTMLElement>(null);
|
|
35
42
|
const destroysRef = useRef<(() => void)[]>([]);
|
|
36
43
|
const [plans, setPlans] = useState<Plans>([]);
|
|
37
44
|
|
|
45
|
+
const [styleTag] = useState(() => {
|
|
46
|
+
const styleTag = document.createElement('style')
|
|
47
|
+
document.head.appendChild(styleTag)
|
|
48
|
+
const prefix = `.${css['ai-chat-messages']} .${css['chat-bubble-container']}:nth-last-child(1)`;
|
|
49
|
+
return {
|
|
50
|
+
setStyle: (height: number) => {
|
|
51
|
+
styleTag.innerHTML = `${prefix} {
|
|
52
|
+
min-height: ${height - 1}px;
|
|
53
|
+
}`
|
|
54
|
+
},
|
|
55
|
+
remove: () => {
|
|
56
|
+
document.head.removeChild(styleTag);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
})
|
|
60
|
+
|
|
38
61
|
useLayoutEffect(() => {
|
|
39
62
|
destroysRef.current.push(rxai.events.on('plan', (plans) => {
|
|
40
63
|
setPlans([...plans])
|
|
41
64
|
}, true))
|
|
65
|
+
|
|
66
|
+
styleTag.setStyle(mainRef.current!.clientHeight);
|
|
42
67
|
}, [])
|
|
43
68
|
|
|
44
69
|
useEffect(() => {
|
|
70
|
+
const autoScroller = new AutoScroller(mainRef.current!, {
|
|
71
|
+
resizeObserverCallback: () => {
|
|
72
|
+
const height = mainRef.current?.clientHeight;
|
|
73
|
+
if (height && height > 0) {
|
|
74
|
+
styleTag.setStyle(height)
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
mutationCallback: (mutations) => {
|
|
78
|
+
const mutationRecord = mutations[0];
|
|
79
|
+
|
|
80
|
+
if (mutationRecord.target === mainRef.current && mutationRecord.addedNodes.length) {
|
|
81
|
+
mainRef.current!.scrollTop = mainRef.current!.scrollHeight;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
45
86
|
return () => {
|
|
87
|
+
autoScroller.destroy();
|
|
88
|
+
|
|
46
89
|
for (const destroy of destroysRef.current) {
|
|
47
90
|
destroy()
|
|
48
91
|
}
|
|
@@ -50,46 +93,74 @@ const Messages = (params: MessagesParams) => {
|
|
|
50
93
|
}, [])
|
|
51
94
|
|
|
52
95
|
return (
|
|
53
|
-
<main className={css['ai-chat-messages']}>
|
|
96
|
+
<main ref={mainRef} className={css['ai-chat-messages']}>
|
|
54
97
|
{plans.map((plan, index) => {
|
|
55
|
-
return
|
|
98
|
+
return (
|
|
99
|
+
<Bubble
|
|
100
|
+
key={plan.id}
|
|
101
|
+
user={user}
|
|
102
|
+
plan={plan}
|
|
103
|
+
copilot={copilot}
|
|
104
|
+
onSend={onSend}
|
|
105
|
+
onMentionClick={onMentionClick}
|
|
106
|
+
/>
|
|
107
|
+
)
|
|
56
108
|
})}
|
|
57
|
-
<div className={css['anchor']} />
|
|
58
109
|
</main>
|
|
59
110
|
)
|
|
60
111
|
}
|
|
61
112
|
|
|
62
113
|
type Plan = Rxai['cacheMessages'][number];
|
|
63
114
|
|
|
64
|
-
type UserFriendlyMessages = Plan['userFriendlyMessages'];
|
|
65
|
-
|
|
66
115
|
interface BubbleParams {
|
|
67
116
|
user: User;
|
|
68
117
|
copilot: User;
|
|
69
118
|
plan: Plan;
|
|
119
|
+
onSend?: SenderProps['onSend'];
|
|
120
|
+
onMentionClick?: (mention: Mention) => void;
|
|
70
121
|
}
|
|
71
122
|
const Bubble = (params: BubbleParams) => {
|
|
72
|
-
const { user, plan, copilot } = params;
|
|
123
|
+
const { user, plan, copilot, onSend, onMentionClick } = params;
|
|
124
|
+
const [userMessage, setUserMessage] = useState<ReturnType<Plan['getUserMessage']>>();
|
|
125
|
+
const destroysRef = useRef<(() => void)[]>([]);
|
|
73
126
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
127
|
+
useLayoutEffect(() => {
|
|
128
|
+
destroysRef.current.push(
|
|
129
|
+
plan.events.on('userMessage', (userMessage) => {
|
|
130
|
+
setUserMessage(userMessage);
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
}, [])
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
return () => {
|
|
137
|
+
for (const destroy of destroysRef.current) {
|
|
138
|
+
destroy()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, [])
|
|
142
|
+
|
|
143
|
+
return userMessage && (
|
|
144
|
+
<div className={css['chat-bubble-container']}>
|
|
145
|
+
<BubbleUser user={user} message={userMessage} plan={plan} onMentionClick={onMentionClick}/>
|
|
146
|
+
<BubbleCopilot copilot={copilot} plan={plan}/>
|
|
147
|
+
<BubbleAction plan={plan} onSend={onSend}/>
|
|
148
|
+
</div>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const BubbleAction = (props: { plan: Plan, onSend?: SenderProps['onSend']; }) => {
|
|
153
|
+
const { plan, onSend } = props;
|
|
77
154
|
|
|
78
155
|
const destroysRef = useRef<(() => void)[]>([]);
|
|
156
|
+
const [status, setStatus] = useState<Plan['status']>();
|
|
157
|
+
const [showRender, setShowRender] = useState(false);
|
|
158
|
+
const senderRef = useRef<SenderRef>(null);
|
|
79
159
|
|
|
80
160
|
useLayoutEffect(() => {
|
|
81
161
|
destroysRef.current.push(
|
|
82
|
-
plan.events.on('
|
|
83
|
-
|
|
84
|
-
}, true),
|
|
85
|
-
plan.events.on('messageStream', (messageStream: string) => {
|
|
86
|
-
setMessage((pre) => {
|
|
87
|
-
return pre + messageStream;
|
|
88
|
-
})
|
|
89
|
-
}, true),
|
|
90
|
-
plan.events.on('userFriendlyMessages', (messages: UserFriendlyMessages) => {
|
|
91
|
-
setMessages([...messages])
|
|
92
|
-
setMessage("")
|
|
162
|
+
plan.events.on('status', (status) => {
|
|
163
|
+
setStatus(status)
|
|
93
164
|
}, true),
|
|
94
165
|
)
|
|
95
166
|
}, [])
|
|
@@ -102,10 +173,39 @@ const Bubble = (params: BubbleParams) => {
|
|
|
102
173
|
}
|
|
103
174
|
}, [])
|
|
104
175
|
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (showRender) {
|
|
178
|
+
senderRef.current!.setMentions((plan.extension as any).mentions || []);
|
|
179
|
+
senderRef.current!.focus();
|
|
180
|
+
}
|
|
181
|
+
}, [showRender])
|
|
182
|
+
|
|
105
183
|
return (
|
|
106
184
|
<>
|
|
107
|
-
|
|
108
|
-
|
|
185
|
+
<div className={css['chat-bubble-action']}>
|
|
186
|
+
{/* TODO 兼容处理,没有pageId,不允许追加 */}
|
|
187
|
+
{status !== "pending" && onSend && (plan.extension as any).mentions?.[0]?.pageId && <div
|
|
188
|
+
className={classNames(css['chat-bubble-action-chat'], {
|
|
189
|
+
[css['focus']]: showRender
|
|
190
|
+
})}
|
|
191
|
+
data-mybricks-tip={"基于本次回答继续对话"}
|
|
192
|
+
onClick={() => {
|
|
193
|
+
setShowRender(!showRender);
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
<Chat />
|
|
197
|
+
</div>}
|
|
198
|
+
</div>
|
|
199
|
+
{showRender && (
|
|
200
|
+
<Sender
|
|
201
|
+
ref={senderRef}
|
|
202
|
+
placeholder={`您好,我是${context.name},请详细描述您的需求`}
|
|
203
|
+
onSend={(params) => {
|
|
204
|
+
onSend!({...params, insertAfter: plan});
|
|
205
|
+
setShowRender(false);
|
|
206
|
+
}}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
109
209
|
</>
|
|
110
210
|
)
|
|
111
211
|
}
|
|
@@ -118,7 +218,7 @@ const BubbleMessage = (params: BubbleMessageParams) => {
|
|
|
118
218
|
const messageRef = useRef<HTMLDivElement>(null);
|
|
119
219
|
|
|
120
220
|
useEffect(() => {
|
|
121
|
-
messageRef.current!.innerHTML =
|
|
221
|
+
messageRef.current!.innerHTML = md.render(message);
|
|
122
222
|
}, [message])
|
|
123
223
|
|
|
124
224
|
return <span ref={messageRef} />
|
|
@@ -126,11 +226,31 @@ const BubbleMessage = (params: BubbleMessageParams) => {
|
|
|
126
226
|
|
|
127
227
|
interface BubbleUserParams {
|
|
128
228
|
user: User;
|
|
129
|
-
message:
|
|
229
|
+
message: ReturnType<Plan['getUserMessage']>
|
|
230
|
+
plan: Plan;
|
|
231
|
+
onMentionClick?: (mention: Mention) => void;
|
|
130
232
|
}
|
|
131
233
|
|
|
132
234
|
const BubbleUser = (params: BubbleUserParams) => {
|
|
133
|
-
const { user, message } = params;
|
|
235
|
+
const { user, message, plan, onMentionClick } = params;
|
|
236
|
+
const mentions = (plan.extension as Extension)?.mentions || [];
|
|
237
|
+
let content = "";
|
|
238
|
+
let attachments: Plan["options"]["attachments"] = [];
|
|
239
|
+
|
|
240
|
+
if (typeof message.content === "string") {
|
|
241
|
+
content = message.content
|
|
242
|
+
} else {
|
|
243
|
+
message.content.forEach((item: any) => {
|
|
244
|
+
if (item.type === "text") {
|
|
245
|
+
content = item.text
|
|
246
|
+
} else if (item.type === "image_url") {
|
|
247
|
+
attachments.push({
|
|
248
|
+
type: "image",
|
|
249
|
+
content: item.image_url.url
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
}
|
|
134
254
|
|
|
135
255
|
return (
|
|
136
256
|
<article className={css['chat-bubble']}>
|
|
@@ -140,14 +260,18 @@ const BubbleUser = (params: BubbleUserParams) => {
|
|
|
140
260
|
</span>
|
|
141
261
|
<span className={css['chat-bubble-header-name']}>{user.name}</span>
|
|
142
262
|
</header>
|
|
143
|
-
<section className={classNames(css['chat-message-container'], css['user-message']
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
263
|
+
<section className={classNames(css['chat-message-container'], css['user-message'], {
|
|
264
|
+
[css['mention']]: mentions.length
|
|
265
|
+
})}>
|
|
266
|
+
{mentions.length ? (
|
|
267
|
+
<MentionTag mention={mentions[0]} focusarea={true} onClick={onMentionClick}/>
|
|
268
|
+
) : null}
|
|
269
|
+
<span>
|
|
270
|
+
{content}
|
|
271
|
+
</span>
|
|
272
|
+
{attachments.length ? (
|
|
273
|
+
<AttachmentsList className={css['attachments-list']} attachments={attachments}/>
|
|
274
|
+
) : null}
|
|
151
275
|
</section>
|
|
152
276
|
</article>
|
|
153
277
|
)
|
|
@@ -155,36 +279,81 @@ const BubbleUser = (params: BubbleUserParams) => {
|
|
|
155
279
|
|
|
156
280
|
interface BubbleCopilotParams {
|
|
157
281
|
copilot: User;
|
|
158
|
-
|
|
159
|
-
message: string;
|
|
160
|
-
loading: boolean;
|
|
282
|
+
plan: Plan;
|
|
161
283
|
}
|
|
162
284
|
const BubbleCopilot = (params: BubbleCopilotParams) => {
|
|
163
|
-
const {
|
|
285
|
+
const { copilot, plan } = params;
|
|
286
|
+
const destroysRef = useRef<(() => void)[]>([]);
|
|
287
|
+
const [loading, setLoading] = useState(false);
|
|
288
|
+
const [streamMessage, setStreamMessage] = useState("");
|
|
289
|
+
const [summary, setSummary] = useState("");
|
|
290
|
+
const [commands, setCommands] = useState<Plan['commands']>([]);
|
|
291
|
+
const [error, setError] = useState("");
|
|
292
|
+
const [planningMessage, setPlanningMessage] = useState("")
|
|
293
|
+
|
|
294
|
+
useLayoutEffect(() => {
|
|
295
|
+
destroysRef.current.push(
|
|
296
|
+
plan.events.on('loading', (loading) => {
|
|
297
|
+
setLoading(loading);
|
|
298
|
+
}),
|
|
299
|
+
plan.events.on('streamMessage', (chunk) => {
|
|
300
|
+
setStreamMessage((streamMessage) => {
|
|
301
|
+
return streamMessage + chunk
|
|
302
|
+
});
|
|
303
|
+
}),
|
|
304
|
+
plan.events.on('summary', (summary) => {
|
|
305
|
+
setSummary(summary);
|
|
306
|
+
}),
|
|
307
|
+
plan.events.on('commands', (commands) => {
|
|
308
|
+
setCommands([...commands]);
|
|
309
|
+
setStreamMessage("");
|
|
310
|
+
}),
|
|
311
|
+
plan.events.on('error', (error) => {
|
|
312
|
+
setError(error);
|
|
313
|
+
}),
|
|
314
|
+
plan.events.on('planningMessage', (planningMessage) => {
|
|
315
|
+
setPlanningMessage(planningMessage);
|
|
316
|
+
})
|
|
317
|
+
)
|
|
318
|
+
}, [])
|
|
319
|
+
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
return () => {
|
|
322
|
+
for (const destroy of destroysRef.current) {
|
|
323
|
+
destroy()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}, [])
|
|
164
327
|
|
|
165
328
|
return (
|
|
166
329
|
<article className={css['chat-bubble']}>
|
|
167
330
|
<header className={css['chat-bubble-header']}>
|
|
168
|
-
<span className={css['chat-bubble-header-avatar']}>
|
|
331
|
+
{/* <span className={css['chat-bubble-header-avatar']}>
|
|
169
332
|
<img className={css['copilot-avatar']} src={copilot.avatar} />
|
|
170
|
-
</span>
|
|
333
|
+
</span> */}
|
|
171
334
|
<span className={css['chat-bubble-header-name']}>{copilot.name}</span>
|
|
172
335
|
</header>
|
|
173
336
|
<section className={classNames(css['chat-message-container'], css['ai-message'])}>
|
|
174
|
-
<div className={css['
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
337
|
+
<div className={css['markdown-body']}>
|
|
338
|
+
<div className={css['think']}>
|
|
339
|
+
{planningMessage ? <BubbleMessage message={`${planningMessage}${loading ? "..." : ""}`} /> : (loading ? <span>正在思考</span> : null)}
|
|
340
|
+
{loading && !planningMessage && <Loading />}
|
|
341
|
+
</div>
|
|
342
|
+
{commands.map((command, index) => {
|
|
343
|
+
if (!command.status || command.status === "error") {
|
|
344
|
+
return null;
|
|
178
345
|
}
|
|
179
|
-
return <
|
|
346
|
+
return <BubbleCopilotTool key={index + command.status} command={command} last={index === commands.length - 1}/>
|
|
180
347
|
})}
|
|
181
|
-
{
|
|
182
|
-
{
|
|
348
|
+
{error && <BubbleError message={error} plan={plan}/>}
|
|
349
|
+
{summary && <BubbleMessage message={summary} />}
|
|
350
|
+
{/* {streamMessage && <BubbleMessage message={streamMessage} />} */}
|
|
351
|
+
{/* {!streamMessage && loading && (
|
|
183
352
|
<div className={css['think']}>
|
|
184
353
|
<span>正在思考</span>
|
|
185
354
|
<Loading />
|
|
186
355
|
</div>
|
|
187
|
-
)}
|
|
356
|
+
)} */}
|
|
188
357
|
</div>
|
|
189
358
|
</section>
|
|
190
359
|
</article>
|
|
@@ -192,45 +361,159 @@ const BubbleCopilot = (params: BubbleCopilotParams) => {
|
|
|
192
361
|
}
|
|
193
362
|
|
|
194
363
|
interface BubbleCopilotToolParams {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
status: "pending" | "success" | "error" | "aborted";
|
|
198
|
-
content: {
|
|
199
|
-
name: string;
|
|
200
|
-
displayName: string;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
364
|
+
command: Plan['commands'][number];
|
|
365
|
+
last: boolean;
|
|
203
366
|
}
|
|
204
367
|
const BubbleCopilotTool = (params: BubbleCopilotToolParams) => {
|
|
205
|
-
const {
|
|
368
|
+
const { command, last } = params;
|
|
369
|
+
const [message, setMessage] = useState("");
|
|
370
|
+
const [expand, setExpand] = useState(false);
|
|
371
|
+
|
|
372
|
+
useEffect(() => {
|
|
373
|
+
if (command.status === "success") {
|
|
374
|
+
setMessage(command.content.display || command.content.llm)
|
|
375
|
+
if (last) {
|
|
376
|
+
setExpand(true)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const destory = command.events?.on("streamMessage", ({ message, status }) => {
|
|
380
|
+
setMessage(message)
|
|
381
|
+
if (status === "start") {
|
|
382
|
+
setExpand(true)
|
|
383
|
+
} else if (status === "complete") {
|
|
384
|
+
if (!last) {
|
|
385
|
+
setExpand(false)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
return () => {
|
|
391
|
+
destory?.();
|
|
392
|
+
}
|
|
393
|
+
}, [])
|
|
394
|
+
|
|
395
|
+
const renderMessage = (shouldRender: boolean) =>
|
|
396
|
+
shouldRender && message ? (
|
|
397
|
+
<div
|
|
398
|
+
className={css['ai-chat-collapsible-response']}
|
|
399
|
+
style={{ display: expand ? "block" : "none" }}
|
|
400
|
+
>
|
|
401
|
+
<BubbleMessage message={message} />
|
|
402
|
+
</div>
|
|
403
|
+
) : null;
|
|
206
404
|
|
|
207
405
|
return (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
</span>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
</
|
|
406
|
+
<>
|
|
407
|
+
{renderMessage(command.status === "pending")}
|
|
408
|
+
<div
|
|
409
|
+
className={classNames(css['ai-chat-collapsible-code-block'], css['collapsed'])}
|
|
410
|
+
onClick={() => {
|
|
411
|
+
if (command.status !== "pending") {
|
|
412
|
+
setExpand((expand) => !expand);
|
|
413
|
+
}
|
|
414
|
+
}}
|
|
415
|
+
>
|
|
416
|
+
<span className={classNames(css['code-header'], css['collapsed'])}>
|
|
417
|
+
<span className={classNames(css['code-title'], css['collapsed'])}>{command.tool.displayName || command.tool.name}</span>
|
|
418
|
+
{command.status === "pending" && (
|
|
419
|
+
<span className={classNames(css['code-title-status'], css['collapsed'], css['pending'])}>
|
|
420
|
+
<Loading />
|
|
421
|
+
</span>
|
|
422
|
+
)}
|
|
423
|
+
{command.status === "success" && (
|
|
424
|
+
<span className={classNames(css['code-title-status'], css['collapsed'], css['success'])}>
|
|
425
|
+
<Success />
|
|
426
|
+
</span>
|
|
427
|
+
)}
|
|
428
|
+
</span>
|
|
429
|
+
</div>
|
|
430
|
+
{renderMessage(command.status === "success")}
|
|
431
|
+
</>
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
interface BubbleErrorParams {
|
|
436
|
+
message: string;
|
|
437
|
+
plan: Plan;
|
|
438
|
+
}
|
|
439
|
+
const BubbleError = (params: BubbleErrorParams) => {
|
|
440
|
+
const { message, plan } = params;
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<div className={css['ai-chat-error-code-block']}>
|
|
444
|
+
<div className={css['ai-chat-error-code-block-message']}>
|
|
445
|
+
<span>{message}</span>
|
|
446
|
+
</div>
|
|
447
|
+
{!plan.enableRetry ? null : (
|
|
448
|
+
<div
|
|
449
|
+
className={css['ai-chat-error-code-block-retry']}
|
|
450
|
+
onClick={() => plan.retry()}
|
|
451
|
+
>重试</div>
|
|
452
|
+
)}
|
|
232
453
|
</div>
|
|
233
454
|
)
|
|
234
455
|
}
|
|
235
456
|
|
|
236
457
|
export { Messages }
|
|
458
|
+
|
|
459
|
+
class AutoScroller {
|
|
460
|
+
private isLockedToBottom: boolean = true;
|
|
461
|
+
private resizeObserver: ResizeObserver | null = null;
|
|
462
|
+
private mutationObserver: MutationObserver | null = null;
|
|
463
|
+
constructor(private container: HTMLElement, private options: { resizeObserverCallback: ResizeObserverCallback, mutationCallback: MutationCallback }) {
|
|
464
|
+
this.isLockedToBottom = true;
|
|
465
|
+
|
|
466
|
+
this.init();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
init() {
|
|
470
|
+
// 监听滚动事件
|
|
471
|
+
this.container.addEventListener('scroll', this.handleScroll.bind(this));
|
|
472
|
+
|
|
473
|
+
// 使用ResizeObserver监听内容大小变化
|
|
474
|
+
this.resizeObserver = new ResizeObserver((entries, observer) => {
|
|
475
|
+
if (this.isLockedToBottom) {
|
|
476
|
+
this.scrollToBottom();
|
|
477
|
+
}
|
|
478
|
+
this.options.resizeObserverCallback(entries, observer);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// 监听容器内部元素大小变化
|
|
482
|
+
this.resizeObserver.observe(this.container);
|
|
483
|
+
|
|
484
|
+
// 同时监听DOM变化
|
|
485
|
+
this.mutationObserver = new MutationObserver((mutations, observer) => {
|
|
486
|
+
if (this.isLockedToBottom) {
|
|
487
|
+
Promise.resolve().then(() => {
|
|
488
|
+
this.scrollToBottom();
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
this.options.mutationCallback(mutations, observer);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
this.mutationObserver.observe(this.container, {
|
|
495
|
+
childList: true,
|
|
496
|
+
subtree: true,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
handleScroll() {
|
|
501
|
+
const isAtBottom = this.isAtBottom();
|
|
502
|
+
this.isLockedToBottom = isAtBottom;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
isAtBottom(threshold = 5) {
|
|
506
|
+
const { scrollTop, scrollHeight, clientHeight } = this.container;
|
|
507
|
+
return Math.abs(scrollHeight - scrollTop - clientHeight) <= threshold;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
scrollToBottom() {
|
|
511
|
+
this.container.scrollTop = this.container.scrollHeight;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
destroy() {
|
|
515
|
+
if (this.mutationObserver) this.mutationObserver.disconnect();
|
|
516
|
+
if (this.resizeObserver) this.resizeObserver.disconnect();
|
|
517
|
+
this.container.removeEventListener('scroll', this.handleScroll);
|
|
518
|
+
}
|
|
519
|
+
}
|