Merge branch 'main' into feat/tool-streaming

This commit is contained in:
dayuan.jiang
2025-11-30 16:17:40 +09:00
4 changed files with 4871 additions and 83 deletions

6
.eslintrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}

View File

@@ -1,15 +1,23 @@
"use client"; "use client";
import type React from "react";
import { useRef, useEffect, useState, useCallback } from "react"; import { useRef, useEffect, useState, useCallback } from "react";
import Image from "next/image"; import Image from "next/image";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import ExamplePanel from "./chat-example-panel"; import ExamplePanel from "./chat-example-panel";
import { UIMessage } from "ai"; import { UIMessage } from "ai";
import { convertToLegalXml, replaceNodes } from "@/lib/utils"; import { convertToLegalXml, replaceNodes } from "@/lib/utils";
import { Copy, Check, X } from "lucide-react";
import { useDiagram } from "@/contexts/diagram-context"; import { useDiagram } from "@/contexts/diagram-context";
const getMessageTextContent = (message: UIMessage): string => {
if (!message.parts) return "";
return message.parts
.filter((part: any) => part.type === "text")
.map((part: any) => part.text)
.join("\n");
};
interface ChatMessageDisplayProps { interface ChatMessageDisplayProps {
messages: UIMessage[]; messages: UIMessage[];
error?: Error | null; error?: Error | null;
@@ -30,6 +38,21 @@ export function ChatMessageDisplay({
const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>( const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>(
{} {}
); );
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null);
const [copyFailedMessageId, setCopyFailedMessageId] = useState<string | null>(null);
const copyMessageToClipboard = async (messageId: string, text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopiedMessageId(messageId);
setTimeout(() => setCopiedMessageId(null), 2000);
} catch (err) {
console.error("Failed to copy message:", err);
setCopyFailedMessageId(messageId);
setTimeout(() => setCopyFailedMessageId(null), 2000);
}
};
const handleDisplayChart = useCallback( const handleDisplayChart = useCallback(
(xml: string) => { (xml: string) => {
const currentXml = xml || ""; const currentXml = xml || "";
@@ -160,16 +183,30 @@ export function ChatMessageDisplay({
{messages.length === 0 ? ( {messages.length === 0 ? (
<ExamplePanel setInput={setInput} setFiles={setFiles} /> <ExamplePanel setInput={setInput} setFiles={setFiles} />
) : ( ) : (
messages.map((message) => ( messages.map((message) => {
const userMessageText = message.role === "user" ? getMessageTextContent(message) : "";
return (
<div <div
key={message.id} key={message.id}
className={`mb-4 ${ className={`mb-4 flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
message.role === "user" ? "text-right" : "text-left"
}`}
> >
{message.role === "user" && userMessageText && (
<button
onClick={() => copyMessageToClipboard(message.id, userMessageText)}
className="p-1 text-gray-400 hover:text-gray-600 transition-colors self-center mr-1"
title={copiedMessageId === message.id ? "Copied!" : copyFailedMessageId === message.id ? "Failed to copy" : "Copy message"}
>
{copiedMessageId === message.id ? (
<Check className="h-3.5 w-3.5 text-green-500" />
) : copyFailedMessageId === message.id ? (
<X className="h-3.5 w-3.5 text-red-500" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</button>
)}
<div <div
className={`inline-block px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${ className={`px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${message.role === "user"
message.role === "user"
? "bg-primary text-primary-foreground" ? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground" : "bg-muted text-muted-foreground"
}`} }`}
@@ -204,7 +241,8 @@ export function ChatMessageDisplay({
})} })}
</div> </div>
</div> </div>
)) );
})
)} )}
{error && ( {error && (
<div className="text-red-500 text-sm mt-2"> <div className="text-red-500 text-sm mt-2">

4814
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -46,6 +46,8 @@
"@types/pako": "^2.0.3", "@types/pako": "^2.0.3",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"eslint": "9.39.1",
"eslint-config-next": "16.0.5",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }