返回 presentation-ai
ToolMessage.tsx
根目录 / src / components / presentation / agent / ToolMessage.tsx
1 import {
2 parseSlideXml,
3 type PlateSlide,
4 } from "@/components/notebook/presentation/utils/parser";
5 import {
6 getMessageText,
7 getToolInputArgs,
8 getToolName,
9 getToolState,
10 isToolPart,
11 } from "@/lib/ai/uiMessageParts";
12 import { type UIMessage } from "ai";
13 import Compare from "./Compare";
14 import { extractSlideContent } from "./HumanMessage";
15 import { PresentationReplaceImageCompare } from "./tools/ReplaceImageCompare";
16
17 export default function ToolMessageComponent({
18 message,
19 messages,
20 }: {
21 message: UIMessage;
22 messages: UIMessage[];
23 }) {
24 const toolParts = message.parts.filter(isToolPart);
25
26 for (const part of toolParts) {
27 const toolName = getToolName(part);
28
29 if (
30 toolName !== "regenerate_slide" &&
31 toolName !== "create_slide" &&
32 toolName !== "delete_slide" &&
33 toolName !== "replace_image"
34 ) {
35 continue;
36 }
37
38 if (getToolState(part) !== "result") {
39 continue;
40 }
41
42 try {
43 const messageIndex = messages.findIndex((candidate) => candidate.id === message.id);
44
45 if (messageIndex === -1) {
46 return null;
47 }
48
49 const priorMessages = messages.slice(0, messageIndex);
50 const lastHumanMessage = [...priorMessages]
51 .reverse()
52 .find((candidate) => candidate.role === "user");
53
54 const slidesString = extractSlideContent(
55 lastHumanMessage ? getMessageText(lastHumanMessage) : "",
56 );
57 const originalSlides = Array.isArray(slidesString)
58 ? []
59 : (parseSlideXml(slidesString ?? "") ?? []);
60 const args = getToolInputArgs(part) as
61 | { slideIds?: string[]; slides?: string[] }
62 | { slides?: string[]; afterSlideId?: string }
63 | { slideIds?: string[] }
64 | {
65 slideIds?: string[];
66 scope?: "all";
67 imageUrl?: string;
68 imagePrompt?: string;
69 };
70
71 if (
72 toolName === "regenerate_slide" &&
73 Array.isArray((args as { slideIds?: string[] }).slideIds) &&
74 Array.isArray((args as { slides?: string[] }).slides)
75 ) {
76 const { slideIds = [], slides = [] } = args as {
77 slideIds: string[];
78 slides: string[];
79 };
80 const modifiedSlides: PlateSlide[] = [];
81 const filteredOriginalSlides: PlateSlide[] = [];
82
83 for (let index = 0; index < Math.min(slideIds.length, slides.length); index++) {
84 const slideId = slideIds[index];
85 const slideContent = slides[index];
86
87 if (!slideId || !slideContent) {
88 continue;
89 }
90
91 try {
92 const parsedSlide = parseSlideXml(slideContent) ?? [];
93
94 if (parsedSlide[0]) {
95 modifiedSlides.push({ ...parsedSlide[0], id: slideId });
96 }
97 } catch (error) {
98 console.error("Error parsing regenerated slide:", error);
99 }
100
101 const originalSlide = originalSlides.find((slide) => slide?.id === slideId);
102
103 if (originalSlide) {
104 filteredOriginalSlides.push(originalSlide);
105 }
106 }
107
108 return (
109 <div className="max-w-full">
110 <Compare left={filteredOriginalSlides} right={modifiedSlides} />
111 </div>
112 );
113 }
114
115 if (
116 toolName === "create_slide" &&
117 Array.isArray((args as { slides?: string[] }).slides)
118 ) {
119 const { slides = [], afterSlideId } = args as {
120 slides?: string[];
121 afterSlideId?: string;
122 };
123 const newSlides: PlateSlide[] = [];
124
125 for (const slideContent of slides) {
126 try {
127 const parsedSlide = parseSlideXml(slideContent) ?? [];
128
129 if (parsedSlide[0]) {
130 newSlides.push(parsedSlide[0]);
131 }
132 } catch (error) {
133 console.error("Error parsing created slide:", error);
134 }
135 }
136
137 const rightSlides = [...originalSlides];
138
139 if (afterSlideId) {
140 const insertionIndex = rightSlides.findIndex(
141 (slide) => slide?.id === afterSlideId,
142 );
143
144 if (insertionIndex >= 0) {
145 rightSlides.splice(insertionIndex + 1, 0, ...newSlides);
146 } else {
147 rightSlides.push(...newSlides);
148 }
149 } else {
150 rightSlides.push(...newSlides);
151 }
152
153 return (
154 <div className="max-w-full">
155 <Compare
156 left={originalSlides}
157 right={rightSlides}
158 shouldReplaceTheSlides={true}
159 />
160 </div>
161 );
162 }
163
164 if (
165 toolName === "delete_slide" &&
166 Array.isArray((args as { slideIds?: string[] }).slideIds)
167 ) {
168 const { slideIds = [] } = args as { slideIds?: string[] };
169 const toDelete = new Set(slideIds);
170 const rightSlides = originalSlides.filter(
171 (slide) => !toDelete.has(slide?.id),
172 );
173
174 return (
175 <div className="max-w-full">
176 <Compare
177 left={originalSlides}
178 right={rightSlides}
179 shouldReplaceTheSlides={true}
180 />
181 </div>
182 );
183 }
184
185 if (toolName === "replace_image") {
186 const {
187 slideIds,
188 scope,
189 imageUrl,
190 imagePrompt,
191 } = args as {
192 slideIds?: string[];
193 scope?: "all";
194 imageUrl?: string;
195 imagePrompt?: string;
196 };
197
198 return (
199 <PresentationReplaceImageCompare
200 originalSlides={originalSlides}
201 slideIds={slideIds}
202 scope={scope}
203 imageUrl={imageUrl}
204 imagePrompt={imagePrompt}
205 />
206 );
207 }
208 } catch (error) {
209 console.error("Error processing tool message:", error);
210 return null;
211 }
212 }
213
214 return null;
215 }
216
216 lines Plain Text