diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
index 83e750a..10ae529 100644
--- a/app/api/chat/route.ts
+++ b/app/api/chat/route.ts
@@ -67,41 +67,23 @@ function isMinimalDiagram(xml: string): boolean {
// Helper function to fix tool call inputs for Bedrock API
// Bedrock requires toolUse.input to be a JSON object, not a string
function fixToolCallInputs(messages: any[]): any[] {
- return messages.map((msg, msgIndex) => {
+ return messages.map((msg) => {
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
return msg
}
- const fixedContent = msg.content.map((part: any, partIndex: number) => {
+ const fixedContent = msg.content.map((part: any) => {
if (part.type === "tool-call") {
- console.log(
- `[fixToolCallInputs] msg[${msgIndex}].content[${partIndex}] tool-call:`,
- {
- toolName: part.toolName,
- inputType: typeof part.input,
- input: part.input,
- },
- )
if (typeof part.input === "string") {
try {
const parsed = JSON.parse(part.input)
- console.log(
- `[fixToolCallInputs] Parsed string input to JSON:`,
- parsed,
- )
return { ...part, input: parsed }
} catch {
// If parsing fails, wrap the string in an object
- console.log(
- `[fixToolCallInputs] Failed to parse, wrapping in object`,
- )
return { ...part, input: { rawInput: part.input } }
}
}
// Input is already an object, but verify it's not null/undefined
if (part.input === null || part.input === undefined) {
- console.log(
- `[fixToolCallInputs] Input is null/undefined, using empty object`,
- )
return { ...part, input: {} }
}
}
diff --git a/components/chat-message-display.tsx b/components/chat-message-display.tsx
index 27bf204..3c618ef 100644
--- a/components/chat-message-display.tsx
+++ b/components/chat-message-display.tsx
@@ -509,139 +509,209 @@ export function ChatMessageDisplay({
) : (
- /* Text content in bubble */
- message.parts?.some(
- (part) =>
- part.type === "text" ||
- part.type === "file",
- ) && (
-
{
+ /* Render parts in order, grouping consecutive text/file parts into bubbles */
+ (() => {
+ const parts = message.parts || []
+ const groups: {
+ type: "content" | "tool"
+ parts: typeof parts
+ startIndex: number
+ }[] = []
+
+ parts.forEach((part, index) => {
+ const isToolPart =
+ part.type?.startsWith(
+ "tool-",
+ )
+ const isContentPart =
+ part.type === "text" ||
+ part.type === "file"
+
+ if (isToolPart) {
+ groups.push({
+ type: "tool",
+ parts: [part],
+ startIndex: index,
+ })
+ } else if (isContentPart) {
+ const lastGroup =
+ groups[
+ groups.length - 1
+ ]
if (
- message.role ===
- "user" &&
- isLastUserMessage &&
- onEditMessage
+ lastGroup?.type ===
+ "content"
) {
- setEditingMessageId(
- message.id,
+ lastGroup.parts.push(
+ part,
)
- setEditText(
- userMessageText,
+ } else {
+ groups.push({
+ type: "content",
+ parts: [part],
+ startIndex: index,
+ })
+ }
+ }
+ })
+
+ return groups.map(
+ (group, groupIndex) => {
+ if (group.type === "tool") {
+ return renderToolPart(
+ group
+ .parts[0] as ToolPartLike,
)
}
- }}
- onKeyDown={(e) => {
- if (
- (e.key === "Enter" ||
- e.key === " ") &&
- message.role ===
- "user" &&
- isLastUserMessage &&
- onEditMessage
- ) {
- e.preventDefault()
- setEditingMessageId(
- message.id,
- )
- setEditText(
- userMessageText,
- )
- }
- }}
- title={
- message.role === "user" &&
- isLastUserMessage &&
- onEditMessage
- ? "Click to edit"
- : undefined
- }
- >
- {message.parts?.map(
- (part, index) => {
- switch (part.type) {
- case "text":
- return (
-
*:first-child]:mt-0 [&>*:last-child]:mb-0 ${
- message.role ===
- "user"
- ? "[&_*]:!text-primary-foreground prose-code:bg-white/20"
- : "dark:prose-invert"
- }`}
- >
-
- {
- part.text
- }
-
-
- )
- case "file":
- return (
-
-
-
- )
- default:
- return null
- }
- },
- )}
-
- )
- )}
- {/* Tool calls outside bubble */}
- {message.parts?.map((part) => {
- if (part.type?.startsWith("tool-")) {
- return renderToolPart(
- part as ToolPartLike,
+
+ // Content bubble
+ return (
+ 0 ? "mt-3" : ""}`}
+ role={
+ message.role ===
+ "user" &&
+ isLastUserMessage &&
+ onEditMessage
+ ? "button"
+ : undefined
+ }
+ tabIndex={
+ message.role ===
+ "user" &&
+ isLastUserMessage &&
+ onEditMessage
+ ? 0
+ : undefined
+ }
+ onClick={() => {
+ if (
+ message.role ===
+ "user" &&
+ isLastUserMessage &&
+ onEditMessage
+ ) {
+ setEditingMessageId(
+ message.id,
+ )
+ setEditText(
+ userMessageText,
+ )
+ }
+ }}
+ onKeyDown={(e) => {
+ if (
+ (e.key ===
+ "Enter" ||
+ e.key ===
+ " ") &&
+ message.role ===
+ "user" &&
+ isLastUserMessage &&
+ onEditMessage
+ ) {
+ e.preventDefault()
+ setEditingMessageId(
+ message.id,
+ )
+ setEditText(
+ userMessageText,
+ )
+ }
+ }}
+ title={
+ message.role ===
+ "user" &&
+ isLastUserMessage &&
+ onEditMessage
+ ? "Click to edit"
+ : undefined
+ }
+ >
+ {group.parts.map(
+ (
+ part,
+ partIndex,
+ ) => {
+ if (
+ part.type ===
+ "text"
+ ) {
+ return (
+
*:first-child]:mt-0 [&>*:last-child]:mb-0 ${
+ message.role ===
+ "user"
+ ? "[&_*]:!text-primary-foreground prose-code:bg-white/20"
+ : "dark:prose-invert"
+ }`}
+ >
+
+ {
+ (
+ part as {
+ text: string
+ }
+ )
+ .text
+ }
+
+
+ )
+ }
+ if (
+ part.type ===
+ "file"
+ ) {
+ return (
+
+
+
+ )
+ }
+ return null
+ },
+ )}
+
+ )
+ },
)
- }
- return null
- })}
+ })()
+ )}
{/* Action buttons for assistant messages */}
{message.role === "assistant" && (