diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e35a9c8..4767c7f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,12 +25,14 @@ We appreciate your interest and the time you spend helping improve the EvoScient ## Development setup 1. **Fork and clone** the repository: + ```bash git clone https://github.com//EvoScientist-WebUI.git cd EvoScientist-WebUI ``` 2. **Install dependencies** (package manager is **npm**): + ```bash npm install ``` @@ -48,7 +50,7 @@ We appreciate your interest and the time you spend helping improve the EvoScient | Command | Description | | ---------------------- | --------------------------------------- | | `npm run dev` | Start the dev server on port 4716 | -| `npm run build` | Production build + assemble `dist/` | +| `npm run build` | Production build + assemble `dist/` | | `npm start` | Serve the production build on port 4716 | | `npm run start:dist` | Run the assembled standalone `dist/` | | `npm run lint` | Lint with ESLint | diff --git a/src/app/components/ChatInterface.tsx b/src/app/components/ChatInterface.tsx index 15e21a6..9142453 100644 --- a/src/app/components/ChatInterface.tsx +++ b/src/app/components/ChatInterface.tsx @@ -750,9 +750,7 @@ export const ChatInterface = React.memo( // The selector call only survives the filter above while the run is // actively selecting (!interrupt), so this resolves to a spinner for // it without a special case. - status: hasPendingAction - ? "interrupted" - : ("pending" as const), + status: hasPendingAction ? "interrupted" : ("pending" as const), } as ToolCall; }); messageMap.set(message.id!, { @@ -957,9 +955,7 @@ export const ChatInterface = React.memo( message={data.message} toolCalls={data.toolCalls} isLoading={isLoading} - isStreaming={ - isLoading && isLastMessage && isAssistant - } + isStreaming={isLoading && isLastMessage && isAssistant} actionRequests={ isLastMessage ? actionRequests : undefined } diff --git a/src/app/components/ChatMessage.tsx b/src/app/components/ChatMessage.tsx index 6c0045f..d829c3b 100644 --- a/src/app/components/ChatMessage.tsx +++ b/src/app/components/ChatMessage.tsx @@ -192,11 +192,25 @@ export const ChatMessage = React.memo( const [thinkingOpen, setThinkingOpen] = useState(false); const [copied, setCopied] = useState(false); + const copyResetTimer = useRef | undefined>( + undefined + ); + useEffect( + () => () => { + if (copyResetTimer.current) clearTimeout(copyResetTimer.current); + }, + [] + ); + useEffect(() => { + setCopied(false); + if (copyResetTimer.current) clearTimeout(copyResetTimer.current); + }, [messageContent]); const handleCopy = useCallback(async () => { if (!messageContent) return; if (await copyText(messageContent)) { setCopied(true); - setTimeout(() => setCopied(false), 2000); + if (copyResetTimer.current) clearTimeout(copyResetTimer.current); + copyResetTimer.current = setTimeout(() => setCopied(false), 2000); } else { toast.error("Couldn't copy to clipboard."); } @@ -312,7 +326,7 @@ export const ChatMessage = React.memo( type="button" onClick={handleCopy} aria-label={copied ? "Copied" : "Copy message"} - className="inline-flex items-center rounded p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" + className="inline-flex items-center rounded p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring" > {copied ? ( ( type="button" onClick={handleCopy} aria-label={copied ? "Copied" : "Copy message"} - className="inline-flex items-center rounded p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" + className="inline-flex items-center rounded p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring" > {copied ? ( ( type="button" onClick={() => onEditMessage?.(messageContent)} aria-label="Edit message" - className="inline-flex items-center rounded p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" + className="inline-flex items-center rounded p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring" > ( {isSubAgentExpanded(subAgent.id) && (
-
+

Input

diff --git a/src/app/components/CodeBlock.tsx b/src/app/components/CodeBlock.tsx index 20a4fa6..89978b6 100644 --- a/src/app/components/CodeBlock.tsx +++ b/src/app/components/CodeBlock.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useCallback, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import { Check, Copy } from "lucide-react"; @@ -14,10 +14,25 @@ interface CodeBlockProps { export const CodeBlock = React.memo(({ language, value }) => { const [copied, setCopied] = useState(false); + // Clear the "copied" timer on unmount so we don't setState on a gone component. + const copyResetTimer = useRef | undefined>( + undefined + ); + useEffect( + () => () => { + if (copyResetTimer.current) clearTimeout(copyResetTimer.current); + }, + [] + ); + useEffect(() => { + setCopied(false); + if (copyResetTimer.current) clearTimeout(copyResetTimer.current); + }, [value]); const handleCopy = useCallback(async () => { if (await copyText(value)) { setCopied(true); - setTimeout(() => setCopied(false), 2000); + if (copyResetTimer.current) clearTimeout(copyResetTimer.current); + copyResetTimer.current = setTimeout(() => setCopied(false), 2000); } else { toast.error("Couldn't copy to clipboard."); } @@ -29,12 +44,18 @@ export const CodeBlock = React.memo(({ language, value }) => { type="button" onClick={handleCopy} aria-label={copied ? "Copied" : "Copy code"} - className="absolute right-2 top-2 z-10 rounded-md border border-border bg-surface/80 p-1.5 text-[var(--color-text-tertiary)] opacity-0 backdrop-blur-sm transition-opacity hover:text-[var(--color-text-primary)] focus-visible:opacity-100 group-hover:opacity-100" + className="absolute right-2 top-2 z-10 rounded-md border border-border bg-[var(--color-surface)] p-1.5 text-[var(--color-text-tertiary)] opacity-0 backdrop-blur-sm transition-opacity hover:text-[var(--color-text-primary)] focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100" > {copied ? ( - +