返回 AiToEarn
message.handler.ts
根目录 / project / aitoearn-web / src / store / agent / task-instance / message.handler.ts
1 /**
2 * TaskInstance - 消息处理模块
3 * 处理消息的创建、更新和状态管理
4 */
5
6 import type { IActionCard, IDisplayMessage, IPublishFlowData, IUploadedMedia } from '../agent.types'
7 import type { IMessageHandlerContext } from './task-instance.types'
8
9 // ============ 消息创建 ============
10
11 /**
12 * 创建用户消息
13 */
14 export function createUserMessage(content: string, medias?: IUploadedMedia[]): IDisplayMessage {
15 return {
16 id: `user-${Date.now()}`,
17 role: 'user',
18 content,
19 medias: medias?.filter(m => m.url && !m.progress),
20 status: 'done',
21 createdAt: Date.now(),
22 }
23 }
24
25 /**
26 * 创建 assistant 消息
27 * @param ctx 消息上下文
28 * @returns 新创建的 assistant 消息
29 */
30 export function createAssistantMessage(ctx: IMessageHandlerContext): IDisplayMessage {
31 const messageId = `assistant-${Date.now()}`
32 ctx.setCurrentAssistantMessageId(messageId)
33 return {
34 id: messageId,
35 role: 'assistant',
36 content: '',
37 status: 'pending',
38 createdAt: Date.now(),
39 }
40 }
41
42 /**
43 * 添加消息到列表
44 */
45 export function addMessage(ctx: IMessageHandlerContext, message: IDisplayMessage): void {
46 ctx.updateData(data => ({
47 messages: [...data.messages, message],
48 }))
49 }
50
51 /**
52 * 设置消息列表(用于加载历史消息)
53 */
54 export function setMessages(ctx: IMessageHandlerContext, messages: IDisplayMessage[]): void {
55 ctx.updateData(() => ({
56 messages,
57 }))
58 }
59
60 // ============ 消息状态更新 ============
61
62 /**
63 * 标记当前 assistant 消息为完成
64 */
65 export function markMessageDone(ctx: IMessageHandlerContext): void {
66 const currentId = ctx.getCurrentAssistantMessageId()
67 ctx.updateData(data => ({
68 messages: data.messages.map(m => (m.id === currentId ? { ...m, status: 'done' } : m)),
69 isGenerating: false,
70 }))
71 }
72
73 /**
74 * 标记当前 assistant 消息为错误
75 */
76 export function markMessageError(ctx: IMessageHandlerContext, errorMessage: string): void {
77 const currentId = ctx.getCurrentAssistantMessageId()
78 ctx.updateData(data => ({
79 messages: data.messages.map(m =>
80 m.id === currentId ? { ...m, status: 'error', errorMessage } : m,
81 ),
82 isGenerating: false,
83 }))
84 }
85
86 /**
87 * 更新当前 assistant 消息内容
88 */
89 export function updateMessageContent(ctx: IMessageHandlerContext, content: string): void {
90 const currentId = ctx.getCurrentAssistantMessageId()
91 ctx.updateData(data => ({
92 messages: data.messages.map((m) => {
93 if (m.id === currentId) {
94 // 同时更新 content 和最后一个 step 的内容(如果存在)
95 // 只有当 content 不为空时才更新 steps 中的内容,避免被截断
96 const updatedSteps
97 = m.steps && m.steps.length > 0
98 ? m.steps.map((step, index) => {
99 if (index === m.steps!.length - 1) {
100 return {
101 ...step,
102 content: content || step.content,
103 isActive: false,
104 }
105 }
106 return step
107 })
108 : undefined
109 return {
110 ...m,
111 // 如果传入的 content 为空,保留原有内容,避免被截断
112 content: content || m.content,
113 status: 'done' as const,
114 ...(updatedSteps ? { steps: updatedSteps } : {}),
115 }
116 }
117 return m
118 }),
119 }))
120 }
121
122 /**
123 * 更新当前 assistant 消息的 actions
124 */
125 export function updateMessageActions(ctx: IMessageHandlerContext, actions: IActionCard[]): void {
126 const currentId = ctx.getCurrentAssistantMessageId()
127 ctx.updateData(data => ({
128 messages: data.messages.map(m =>
129 m.id === currentId ? { ...m, actions, status: 'done' as const } : m,
130 ),
131 }))
132 }
133
134 /**
135 * 更新当前 assistant 消息内容和 actions
136 */
137 export function updateMessageWithActions(
138 ctx: IMessageHandlerContext,
139 content: string,
140 actions: IActionCard[],
141 ): void {
142 const currentId = ctx.getCurrentAssistantMessageId()
143 ctx.updateData(data => ({
144 messages: data.messages.map((m) => {
145 if (m.id === currentId) {
146 // 只有当 content 不为空时才更新 steps 中的内容,避免被截断
147 const updatedSteps
148 = m.steps && m.steps.length > 0
149 ? m.steps.map((step, index) => {
150 if (index === m.steps!.length - 1) {
151 return {
152 ...step,
153 content: content || step.content,
154 isActive: false,
155 }
156 }
157 return step
158 })
159 : undefined
160 return {
161 ...m,
162 // 如果传入的 content 为空,保留原有内容,避免被截断
163 content: content || m.content,
164 status: 'done' as const,
165 actions,
166 ...(updatedSteps ? { steps: updatedSteps } : {}),
167 }
168 }
169 return m
170 }),
171 }))
172 }
173
174 /**
175 * 更新当前 assistant 消息内容,并将 medias 附加到最后一个 step
176 */
177 export function updateMessageContentWithMedias(
178 ctx: IMessageHandlerContext,
179 content: string,
180 medias?: Array<{ type: string, url: string, thumbUrl?: string }>,
181 ): void {
182 const currentId = ctx.getCurrentAssistantMessageId()
183 ctx.updateData(data => ({
184 messages: data.messages.map((m) => {
185 if (m.id === currentId) {
186 // 转换 medias 格式
187 const convertedMedias = medias?.map(media => ({
188 url: media.url || media.thumbUrl || '',
189 type: media.type === 'VIDEO' ? ('video' as const) : ('image' as const),
190 }))
191
192 // 更新 steps,将 medias 附加到最后一个 step
193 // 只有当 content 不为空时才更新 steps 中的内容,避免被截断
194 const updatedSteps
195 = m.steps && m.steps.length > 0
196 ? m.steps.map((step, index) => {
197 if (index === m.steps!.length - 1) {
198 return {
199 ...step,
200 content: content || step.content,
201 isActive: false,
202 ...(convertedMedias && convertedMedias.length > 0
203 ? { medias: convertedMedias }
204 : {}),
205 }
206 }
207 return step
208 })
209 : undefined
210
211 return {
212 ...m,
213 // 如果传入的 content 为空,保留原有内容,避免被截断
214 content: content || m.content,
215 status: 'done' as const,
216 ...(updatedSteps ? { steps: updatedSteps } : {}),
217 }
218 }
219 return m
220 }),
221 }))
222 }
223
224 /**
225 * 更新当前 assistant 消息的发布流程数据
226 */
227 export function updateMessageWithPublishFlows(
228 ctx: IMessageHandlerContext,
229 publishFlows: IPublishFlowData[],
230 ): void {
231 const currentId = ctx.getCurrentAssistantMessageId()
232 ctx.updateData(data => ({
233 messages: data.messages.map(m =>
234 m.id === currentId ? { ...m, publishFlows, status: 'done' as const } : m,
235 ),
236 }))
237 }
238
239 /**
240 * 更新当前 assistant 消息内容、actions 和发布流程数据
241 */
242 export function updateMessageWithActionsAndPublishFlows(
243 ctx: IMessageHandlerContext,
244 content: string,
245 actions: IActionCard[],
246 publishFlows: IPublishFlowData[],
247 ): void {
248 const currentId = ctx.getCurrentAssistantMessageId()
249 ctx.updateData(data => ({
250 messages: data.messages.map((m) => {
251 if (m.id === currentId) {
252 // 只有当 content 不为空时才更新 steps 中的内容,避免被截断
253 const updatedSteps
254 = m.steps && m.steps.length > 0
255 ? m.steps.map((step, index) => {
256 if (index === m.steps!.length - 1) {
257 return {
258 ...step,
259 content: content || step.content,
260 isActive: false,
261 }
262 }
263 return step
264 })
265 : undefined
266 return {
267 ...m,
268 // 如果传入的 content 为空,保留原有内容,避免被截断
269 content: content || m.content,
270 status: 'done' as const,
271 actions: actions.length > 0 ? actions : m.actions,
272 publishFlows: publishFlows.length > 0 ? publishFlows : m.publishFlows,
273 ...(updatedSteps ? { steps: updatedSteps } : {}),
274 }
275 }
276 return m
277 }),
278 }))
279 }
280
281 // ============ Markdown 消息 ============
282
283 /**
284 * 添加到 markdown 消息历史
285 */
286 export function addMarkdownMessage(ctx: IMessageHandlerContext, message: string): void {
287 ctx.updateData(data => ({
288 markdownMessages: [...data.markdownMessages, message],
289 }))
290 }
291
292 /**
293 * 更新最后一条 markdown 消息
294 */
295 export function updateLastMarkdownMessage(ctx: IMessageHandlerContext, message: string): void {
296 ctx.updateData((data) => {
297 const newMessages = [...data.markdownMessages]
298 if (newMessages.length > 0 && newMessages[newMessages.length - 1].startsWith('🤖 ')) {
299 newMessages[newMessages.length - 1] = message
300 }
301 else {
302 newMessages.push(message)
303 }
304 return { markdownMessages: newMessages }
305 })
306 }
307
307 lines TYPESCRIPT