返回 AiToEarn
message.ts
根目录 / project / aitoearn-web / src / store / agent / utils / message.ts
1 /**
2 * Agent Store - 消息工具
3 * 消息创建和状态管理工具(支持按任务ID隔离)
4 */
5
6 import type {
7 IActionCard,
8 IAgentState,
9 IDisplayMessage,
10 IPublishFlowData,
11 ITaskMessageData,
12 IUploadedMedia,
13 } from '../agent.types'
14 import type { IAgentRefs } from './refs'
15 import { getDefaultTaskData } from '../agent.state'
16
17 /** 消息工具上下文 */
18 export interface IMessageContext {
19 refs: IAgentRefs
20 set: (partial: Partial<IAgentState> | ((state: IAgentState) => Partial<IAgentState>)) => void
21 get: () => IAgentState
22 }
23
24 /**
25 * 创建消息工具方法
26 */
27 export function createMessageUtils(ctx: IMessageContext) {
28 const { refs, set, get } = ctx
29
30 /**
31 * 获取当前任务ID
32 */
33 function getCurrentTaskId(): string {
34 return get().currentTaskId
35 }
36
37 /**
38 * 获取当前任务数据
39 */
40 function getCurrentTaskData(): ITaskMessageData {
41 const state = get()
42 const taskId = state.currentTaskId
43 return state.taskMessages[taskId] || getDefaultTaskData()
44 }
45
46 /**
47 * 更新当前任务的消息数据
48 * @param updater 更新函数
49 * @param targetTaskId 可选的目标任务ID,不传则使用当前任务ID
50 */
51 function updateCurrentTaskData(
52 updater: (data: ITaskMessageData) => Partial<ITaskMessageData>,
53 targetTaskId?: string,
54 ) {
55 const taskId = targetTaskId || getCurrentTaskId()
56 if (!taskId) {
57 console.warn('[MessageUtils] No taskId set, cannot update task data')
58 return
59 }
60 set((state) => {
61 const currentData = state.taskMessages[taskId] || getDefaultTaskData()
62 const updates = updater(currentData)
63 return {
64 taskMessages: {
65 ...state.taskMessages,
66 [taskId]: {
67 ...currentData,
68 ...updates,
69 lastUpdated: Date.now(),
70 },
71 },
72 }
73 })
74 }
75
76 return {
77 /**
78 * 创建用户消息
79 */
80 createUserMessage(content: string, medias?: IUploadedMedia[]): IDisplayMessage {
81 return {
82 id: `user-${Date.now()}`,
83 role: 'user',
84 content,
85 medias: medias?.filter(m => m.url && !m.progress),
86 status: 'done',
87 createdAt: Date.now(),
88 }
89 },
90
91 /**
92 * 创建 assistant 消息
93 */
94 createAssistantMessage(): IDisplayMessage {
95 const messageId = `assistant-${Date.now()}`
96 refs.currentAssistantMessageId.value = messageId
97
98 return {
99 id: messageId,
100 role: 'assistant',
101 content: '',
102 status: 'pending',
103 createdAt: Date.now(),
104 }
105 },
106
107 /**
108 * 标记当前 assistant 消息为完成
109 * @param targetTaskId 可选的目标任务ID
110 */
111 markMessageDone(targetTaskId?: string) {
112 updateCurrentTaskData(
113 data => ({
114 messages: data.messages.map(m =>
115 m.id === refs.currentAssistantMessageId.value ? { ...m, status: 'done' } : m,
116 ),
117 isGenerating: false,
118 }),
119 targetTaskId,
120 )
121 },
122
123 /**
124 * 标记当前 assistant 消息为错误
125 * @param errorMessage 错误消息
126 * @param targetTaskId 可选的目标任务ID
127 */
128 markMessageError(errorMessage: string, targetTaskId?: string) {
129 updateCurrentTaskData(
130 data => ({
131 messages: data.messages.map(m =>
132 m.id === refs.currentAssistantMessageId.value
133 ? { ...m, status: 'error', errorMessage }
134 : m,
135 ),
136 }),
137 targetTaskId,
138 )
139 },
140
141 /**
142 * 更新当前 assistant 消息内容
143 * @param content 消息内容
144 * @param targetTaskId 可选的目标任务ID
145 */
146 updateMessageContent(content: string, targetTaskId?: string) {
147 updateCurrentTaskData(
148 data => ({
149 messages: data.messages.map((m) => {
150 if (m.id === refs.currentAssistantMessageId.value) {
151 // 同时更新 content 和最后一个 step 的内容(如果存在)
152 // 这样确保 steps 和 content 保持同步
153 const updatedSteps
154 = m.steps && m.steps.length > 0
155 ? m.steps.map((step, index) => {
156 // 只更新最后一个 step(当前活跃的 step)
157 if (index === m.steps!.length - 1) {
158 return { ...step, content, isActive: false }
159 }
160 return step
161 })
162 : undefined
163 return {
164 ...m,
165 content,
166 status: 'done' as const,
167 ...(updatedSteps ? { steps: updatedSteps } : {}),
168 }
169 }
170 return m
171 }),
172 }),
173 targetTaskId,
174 )
175 },
176
177 /**
178 * 更新当前 assistant 消息的 actions(同时标记为完成)
179 * @param actions 动作卡片列表
180 * @param targetTaskId 可选的目标任务ID
181 */
182 updateMessageActions(actions: IActionCard[], targetTaskId?: string) {
183 updateCurrentTaskData(
184 data => ({
185 messages: data.messages.map(m =>
186 m.id === refs.currentAssistantMessageId.value
187 ? { ...m, actions, status: 'done' as const }
188 : m,
189 ),
190 }),
191 targetTaskId,
192 )
193 },
194
195 /**
196 * 更新当前 assistant 消息内容和 actions
197 */
198 updateMessageWithActions(content: string, actions: IActionCard[]) {
199 updateCurrentTaskData(data => ({
200 messages: data.messages.map((m) => {
201 if (m.id === refs.currentAssistantMessageId.value) {
202 // 同时更新 content 和最后一个 step 的内容(如果存在)
203 const updatedSteps
204 = m.steps && m.steps.length > 0
205 ? m.steps.map((step, index) => {
206 if (index === m.steps!.length - 1) {
207 return { ...step, content, isActive: false }
208 }
209 return step
210 })
211 : undefined
212 return {
213 ...m,
214 content,
215 status: 'done' as const,
216 actions,
217 ...(updatedSteps ? { steps: updatedSteps } : {}),
218 }
219 }
220 return m
221 }),
222 }))
223 },
224
225 /**
226 * 更新当前 assistant 消息内容,并将 medias 附加到最后一个 step
227 * 用于 SSE result 消息处理,确保视频/图片等媒体能正确显示
228 */
229 updateMessageContentWithMedias(
230 content: string,
231 medias?: Array<{ type: string, url: string, thumbUrl?: string }>,
232 ) {
233 updateCurrentTaskData(data => ({
234 messages: data.messages.map((m) => {
235 if (m.id === refs.currentAssistantMessageId.value) {
236 // 转换 medias 格式
237 const convertedMedias = medias?.map(media => ({
238 url: media.url || media.thumbUrl || '',
239 type: media.type === 'VIDEO' ? ('video' as const) : ('image' as const),
240 }))
241
242 // 更新 steps,将 medias 附加到最后一个 step
243 const updatedSteps
244 = m.steps && m.steps.length > 0
245 ? m.steps.map((step, index) => {
246 if (index === m.steps!.length - 1) {
247 return {
248 ...step,
249 content,
250 isActive: false,
251 ...(convertedMedias && convertedMedias.length > 0
252 ? { medias: convertedMedias }
253 : {}),
254 }
255 }
256 return step
257 })
258 : undefined
259
260 return {
261 ...m,
262 content,
263 status: 'done' as const,
264 ...(updatedSteps ? { steps: updatedSteps } : {}),
265 }
266 }
267 return m
268 }),
269 }))
270 },
271
272 /**
273 * 添加消息到列表
274 * @param message 要添加的消息
275 * @param targetTaskId 可选的目标任务ID,不传则使用当前任务ID
276 */
277 addMessage(message: IDisplayMessage, targetTaskId?: string) {
278 const taskId = targetTaskId || getCurrentTaskId()
279 if (!taskId) {
280 console.warn('[MessageUtils] No taskId for addMessage, skipping')
281 return
282 }
283 set((state) => {
284 const currentData = state.taskMessages[taskId] || getDefaultTaskData()
285 return {
286 taskMessages: {
287 ...state.taskMessages,
288 [taskId]: {
289 ...currentData,
290 messages: [...currentData.messages, message],
291 lastUpdated: Date.now(),
292 },
293 },
294 }
295 })
296 },
297
298 /**
299 * 设置消息列表(用于加载历史消息到指定任务)
300 */
301 setMessages(messages: IDisplayMessage[], taskId?: string) {
302 const targetTaskId = taskId || getCurrentTaskId()
303 if (!targetTaskId) {
304 console.warn('[MessageUtils] No taskId provided for setMessages')
305 return
306 }
307 set((state) => {
308 const currentData = state.taskMessages[targetTaskId] || getDefaultTaskData()
309 return {
310 taskMessages: {
311 ...state.taskMessages,
312 [targetTaskId]: {
313 ...currentData,
314 messages,
315 lastUpdated: Date.now(),
316 },
317 },
318 }
319 })
320 },
321
322 /**
323 * 添加到 markdown 消息历史
324 */
325 addMarkdownMessage(message: string) {
326 updateCurrentTaskData(data => ({
327 markdownMessages: [...data.markdownMessages, message],
328 }))
329 },
330
331 /**
332 * 更新最后一条 markdown 消息
333 */
334 updateLastMarkdownMessage(message: string) {
335 updateCurrentTaskData((data) => {
336 const newMessages = [...data.markdownMessages]
337 if (newMessages.length > 0 && newMessages[newMessages.length - 1].startsWith('🤖 ')) {
338 newMessages[newMessages.length - 1] = message
339 }
340 else {
341 newMessages.push(message)
342 }
343 return { markdownMessages: newMessages }
344 })
345 },
346
347 /**
348 * 更新当前 assistant 消息的发布流程数据
349 * 用于在消息中显示 PublishDetailCard
350 */
351 updateMessageWithPublishFlows(publishFlows: IPublishFlowData[]) {
352 updateCurrentTaskData(data => ({
353 messages: data.messages.map(m =>
354 m.id === refs.currentAssistantMessageId.value
355 ? { ...m, publishFlows, status: 'done' as const }
356 : m,
357 ),
358 }))
359 },
360
361 /**
362 * 更新当前 assistant 消息内容、actions 和发布流程数据
363 */
364 updateMessageWithActionsAndPublishFlows(
365 content: string,
366 actions: IActionCard[],
367 publishFlows: IPublishFlowData[],
368 ) {
369 updateCurrentTaskData(data => ({
370 messages: data.messages.map((m) => {
371 if (m.id === refs.currentAssistantMessageId.value) {
372 const updatedSteps
373 = m.steps && m.steps.length > 0
374 ? m.steps.map((step, index) => {
375 if (index === m.steps!.length - 1) {
376 return { ...step, content, isActive: false }
377 }
378 return step
379 })
380 : undefined
381 return {
382 ...m,
383 content,
384 status: 'done' as const,
385 actions: actions.length > 0 ? actions : m.actions,
386 publishFlows: publishFlows.length > 0 ? publishFlows : m.publishFlows,
387 ...(updatedSteps ? { steps: updatedSteps } : {}),
388 }
389 }
390 return m
391 }),
392 }))
393 },
394
395 /**
396 * 获取当前任务数据(暴露给外部使用)
397 */
398 getCurrentTaskData,
399
400 /**
401 * 更新当前任务数据(暴露给外部使用)
402 */
403 updateCurrentTaskData,
404 }
405 }
406
407 export type MessageUtils = ReturnType<typeof createMessageUtils>
408
408 lines TYPESCRIPT