@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.
Files changed (93) hide show
  1. package/package.json +7 -2
  2. package/src/agents/app.ts +188 -60
  3. package/src/agents/common.ts +428 -68
  4. package/src/agents/custom.ts +14 -0
  5. package/src/agents/index.ts +31 -1
  6. package/src/agents/knowledges/README.md +614 -0
  7. package/src/agents/knowledges/SUMMARY.md +527 -0
  8. package/src/agents/knowledges/index.ts +8 -0
  9. package/src/agents/knowledges/knowledge-base.ts +565 -0
  10. package/src/agents/knowledges/knowledge-node.ts +266 -0
  11. package/src/agents/knowledges/types.ts +208 -0
  12. package/src/agents/utils/config.ts +427 -0
  13. package/src/agents/workspace/coding-manager.ts +31 -0
  14. package/src/agents/workspace/components-manager.ts +124 -0
  15. package/src/agents/workspace/outline-focus.ts +188 -0
  16. package/src/agents/workspace/outline-info.ts +520 -0
  17. package/src/agents/workspace/page-tree-generator.ts +83 -0
  18. package/src/agents/workspace/workspace.ts +319 -0
  19. package/src/agents/workspace-by-knowledges/MIGRATION.md +568 -0
  20. package/src/agents/workspace-by-knowledges/README.md +521 -0
  21. package/src/agents/workspace-by-knowledges/index.ts +11 -0
  22. package/src/agents/workspace-by-knowledges/providers/component-docs-provider.ts +92 -0
  23. package/src/agents/workspace-by-knowledges/providers/focus-info-provider.ts +131 -0
  24. package/src/agents/workspace-by-knowledges/providers/index.ts +8 -0
  25. package/src/agents/workspace-by-knowledges/providers/project-info-provider.ts +151 -0
  26. package/src/agents/workspace-by-knowledges/test.ts +240 -0
  27. package/src/agents/workspace-by-knowledges/types.ts +56 -0
  28. package/src/agents/workspace-by-knowledges/utils/components-manager.ts +145 -0
  29. package/src/agents/workspace-by-knowledges/utils/index.ts +8 -0
  30. package/src/agents/workspace-by-knowledges/utils/outline-focus.ts +178 -0
  31. package/src/agents/workspace-by-knowledges/utils/outline-info.ts +521 -0
  32. package/src/agents/workspace-by-knowledges/workspace.ts +166 -0
  33. package/src/api/cloud-components.ts +129 -0
  34. package/src/api-record-replay/README.md +187 -0
  35. package/src/api-record-replay/index.ts +11 -0
  36. package/src/api-record-replay/manager.ts +168 -0
  37. package/src/api-record-replay/recorder.ts +117 -0
  38. package/src/api-record-replay/replayer.ts +148 -0
  39. package/src/components/attachments/index.less +117 -0
  40. package/src/components/attachments/index.tsx +136 -0
  41. package/src/components/icons/index.tsx +21 -1
  42. package/src/components/index.less +34 -0
  43. package/src/components/mention/index.less +23 -0
  44. package/src/components/mention/index.tsx +19 -0
  45. package/src/components/messages/index.less +444 -237
  46. package/src/components/messages/index.tsx +371 -88
  47. package/src/components/sender/index.less +203 -0
  48. package/src/components/sender/index.tsx +298 -0
  49. package/src/components/types.ts +31 -0
  50. package/src/constants/index.ts +8 -0
  51. package/src/context/RequestStatusTracker.ts +50 -0
  52. package/src/context/index.ts +68 -6
  53. package/src/{types.d.ts → global.d.ts} +40 -5
  54. package/src/index.tsx +212 -32
  55. package/src/preset/agents.ts +380 -0
  56. package/src/preset/createTemplates.ts +25 -0
  57. package/src/preset/index.ts +12 -0
  58. package/src/preset/prompts.ts +235 -0
  59. package/src/preset/requestAsStream.ts +246 -0
  60. package/src/preset/user.ts +6 -0
  61. package/src/startView/components/header/header.less +17 -0
  62. package/src/startView/components/header/header.tsx +15 -0
  63. package/src/startView/components/index.ts +1 -0
  64. package/src/startView/index.less +22 -204
  65. package/src/startView/index.tsx +35 -203
  66. package/src/tools/analyze-and-expand-prd.ts +192 -86
  67. package/src/tools/analyze-requirement-and-components.ts +589 -0
  68. package/src/tools/answer.ts +59 -0
  69. package/src/tools/build-process.ts +1174 -0
  70. package/src/tools/coding-subagent-as-tool.ts +119 -0
  71. package/src/tools/generate-ui-content.ts +1083 -0
  72. package/src/tools/index.ts +22 -19
  73. package/src/tools/open-dsl.ts +69 -0
  74. package/src/tools/refactor-ui-content.ts +801 -0
  75. package/src/tools/utils.ts +880 -28
  76. package/src/types/index.ts +4 -0
  77. package/src/view/components/header/header.less +36 -2
  78. package/src/view/components/header/header.tsx +47 -2
  79. package/src/view/components/index.ts +0 -2
  80. package/src/view/index.tsx +158 -8
  81. package/src/tools/answer-user.ts +0 -35
  82. package/src/tools/focus-element.ts +0 -47
  83. package/src/tools/generate-page.ts +0 -750
  84. package/src/tools/get-component-info-by-ids.ts +0 -166
  85. package/src/tools/get-component-info.ts +0 -53
  86. package/src/tools/get-components-doc-and-prd.ts +0 -137
  87. package/src/tools/get-focus-mybricks-dsl.ts +0 -26
  88. package/src/tools/get-mybricks-dsl.ts +0 -73
  89. package/src/tools/modify-component.ts +0 -385
  90. package/src/view/components/messages/messages.less +0 -228
  91. package/src/view/components/messages/messages.tsx +0 -172
  92. package/src/view/components/sender/sender.less +0 -44
  93. 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 { marked } from "marked";
5
- import { Loading, Success, Close } from "../icons";
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 renderer = new marked.Renderer();
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 <Bubble key={index} user={user} plan={plan} copilot={copilot} />
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
- const [messages, setMessages] = useState<UserFriendlyMessages>([])
75
- const [message, setMessage] = useState("")
76
- const [loading, setLoading] = useState(true)
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('loading', (loading: boolean) => {
83
- setLoading(loading);
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
- {messages[0] && <BubbleUser user={user} message={messages[0]} />}
108
- {messages[0] && <BubbleCopilot messages={messages.slice(1)} copilot={copilot} message={message} loading={loading} />}
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 = marked.parse(message) as string;
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: UserFriendlyMessages[number]
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
- <div className={css['ai-chat-markdown']}>
145
- <span>{typeof message.content === "string" ? message.content : message.content.find((content: any) => {
146
- if (content.type === "text") {
147
- return content
148
- }
149
- }).text}</span>
150
- </div>
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
- messages: UserFriendlyMessages;
159
- message: string;
160
- loading: boolean;
282
+ plan: Plan;
161
283
  }
162
284
  const BubbleCopilot = (params: BubbleCopilotParams) => {
163
- const { messages, copilot, message, loading } = params;
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['ai-chat-markdown']}>
175
- {messages.map((message, index) => {
176
- if (message.role === "tool") {
177
- return <BubbleCopilotTool key={index + message.status} message={message} />
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 <BubbleMessage message={message.content} />
346
+ return <BubbleCopilotTool key={index + command.status} command={command} last={index === commands.length - 1}/>
180
347
  })}
181
- {message && <BubbleMessage message={message} />}
182
- {!message && loading && (
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
- message: {
196
- type: "tool";
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 { message } = params;
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
- <div className={classNames(css['ai-chat-collapsible-code-block'], css['collapsed'])}>
209
- <span className={classNames(css['code-header'], css['collapsed'])}>
210
- <span className={classNames(css['code-title'], css['collapsed'])}>{message.content.displayName || message.content.name}</span>
211
- {message.status === "pending" && (
212
- <span className={classNames(css['code-title-status'], css['collapsed'], css['pending'])}>
213
- <Loading />
214
- </span>
215
- )}
216
- {message.status === "success" && (
217
- <span className={classNames(css['code-title-status'], css['collapsed'], css['success'])}>
218
- <Success />
219
- </span>
220
- )}
221
- {message.status === "error" && (
222
- <span className={classNames(css['code-title-status'], css['collapsed'], css['error'])}>
223
- <Close />
224
- </span>
225
- )}
226
- {message.status === "aborted" && (
227
- <span className={classNames(css['code-title-status'], css['collapsed'], css['aborted'])}>
228
- 取消
229
- </span>
230
- )}
231
- </span>
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
+ }