mirror of
https://github.com/aljazceru/goose.git
synced 2026-01-02 22:14:26 +01:00
@@ -235,7 +235,7 @@ export default function ChatView({
|
||||
{messages.length === 0 ? (
|
||||
<Splash append={(text) => append(createUserMessage(text))} />
|
||||
) : (
|
||||
<ScrollArea ref={scrollRef} className="flex-1 pl-4" autoScroll>
|
||||
<ScrollArea ref={scrollRef} className="flex-1 px-4" autoScroll>
|
||||
{filteredMessages.map((message, index) => (
|
||||
<div key={message.id || index} className="mt-[16px]">
|
||||
{isUserMessage(message) ? (
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
getToolConfirmationContent,
|
||||
} from '../types/message';
|
||||
import ToolCallConfirmation from './ToolCallConfirmation';
|
||||
import CopyButton from './ui/CopyButton';
|
||||
|
||||
interface GooseMessageProps {
|
||||
message: Message;
|
||||
@@ -68,17 +67,9 @@ export default function GooseMessage({ message, metadata, messages, append }: Go
|
||||
{/* Always show the top content area if there are tool calls, even if textContent is empty */}
|
||||
{(textContent || toolRequests.length > 0) && (
|
||||
<div
|
||||
className={`goose-message-content bg-bgSubtle rounded-2xl px-4 py-2 ${toolRequests.length > 0 ? 'rounded-b-none' : ''} relative group`}
|
||||
className={`goose-message-content bg-bgSubtle rounded-2xl px-4 py-2 ${toolRequests.length > 0 ? 'rounded-b-none' : ''}`}
|
||||
>
|
||||
{textContent ? <MarkdownContent content={textContent} /> : null}
|
||||
{/* Only show CopyButton if there's text content and no tool requests/responses */}
|
||||
{textContent && message.content.every((content) => content.type === 'text') && (
|
||||
<CopyButton
|
||||
text={textContent}
|
||||
className="absolute -bottom-2 -right-2 p-1.5 rounded-full bg-white dark:bg-gray-800 shadow-md z-[1000] hover:bg-gray-100 dark:hover:bg-gray-700 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
iconClassName="h-4 w-4 text-gray-800 dark:text-white"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import { oneLight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||
import { Check, Copy } from './icons';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import CopyButton from './ui/CopyButton';
|
||||
|
||||
const UrlTransform = {
|
||||
a: ({ node, ...props }) => <a {...props} target="_blank" rel="noopener noreferrer" />,
|
||||
@@ -30,14 +31,28 @@ interface MarkdownContentProps {
|
||||
}
|
||||
|
||||
const CodeBlock = ({ language, children }: { language: string; children: string }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(children);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000); // Reset after 2 seconds
|
||||
} catch (err) {
|
||||
console.error('Failed to copy text: ', err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative group w-full">
|
||||
<CopyButton
|
||||
text={children}
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="absolute right-2 bottom-2 p-1.5 rounded-lg bg-gray-700/50 text-gray-300
|
||||
opacity-0 group-hover:opacity-100 transition-opacity duration-200
|
||||
hover:bg-gray-600/50 hover:text-gray-100 z-10"
|
||||
/>
|
||||
title="Copy code"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||
</button>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<SyntaxHighlighter
|
||||
style={oneDark}
|
||||
@@ -64,6 +79,8 @@ const CodeBlock = ({ language, children }: { language: string; children: string
|
||||
};
|
||||
|
||||
export default function MarkdownContent({ content, className = '' }: MarkdownContentProps) {
|
||||
// Determine whether dark mode is enabled
|
||||
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||
return (
|
||||
<div className="w-full overflow-x-hidden">
|
||||
<ReactMarkdown
|
||||
|
||||
@@ -3,7 +3,6 @@ import LinkPreview from './LinkPreview';
|
||||
import { extractUrls } from '../utils/urlUtils';
|
||||
import MarkdownContent from './MarkdownContent';
|
||||
import { Message, getTextContent } from '../types/message';
|
||||
import CopyButton from './ui/CopyButton';
|
||||
|
||||
interface UserMessageProps {
|
||||
message: Message;
|
||||
@@ -19,13 +18,8 @@ export default function UserMessage({ message }: UserMessageProps) {
|
||||
return (
|
||||
<div className="flex justify-end mt-[16px] w-full opacity-0 animate-[appear_150ms_ease-in_forwards]">
|
||||
<div className="flex-col max-w-[85%]">
|
||||
<div className="flex bg-slate text-white rounded-xl rounded-br-none py-2 px-3 mr-4 relative group">
|
||||
<div className="flex bg-slate text-white rounded-xl rounded-br-none py-2 px-3">
|
||||
<MarkdownContent content={textContent} className="text-white" />
|
||||
<CopyButton
|
||||
text={textContent}
|
||||
className="absolute -bottom-2 -right-2 p-1.5 rounded-full bg-white dark:bg-gray-800 shadow-md z-[1000] hover:bg-gray-100 dark:hover:bg-gray-700 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
iconClassName="h-4 w-4 text-gray-800 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* TODO(alexhancock): Re-enable link previews once styled well again */}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Check, Copy } from '../icons';
|
||||
|
||||
interface CopyButtonProps {
|
||||
text: string;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
lightIcon?: boolean;
|
||||
}
|
||||
|
||||
export default function CopyButton({
|
||||
text,
|
||||
className = 'absolute bottom-2 right-2 p-1.5 rounded-lg bg-gray-700/50 text-gray-300 opacity-0 group-hover:opacity-100 transition-opacity duration-200 hover:bg-gray-600/50 hover:text-gray-100',
|
||||
iconClassName = 'h-4 w-4',
|
||||
lightIcon = false,
|
||||
}: CopyButtonProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000); // Reset after 2 seconds
|
||||
} catch (err) {
|
||||
console.error('Failed to copy text: ', err);
|
||||
}
|
||||
};
|
||||
|
||||
const Icon = copied ? Check : Copy;
|
||||
|
||||
return (
|
||||
<button onClick={handleCopy} className={className} title="Copy text">
|
||||
<Icon className={`${iconClassName} ${lightIcon ? 'text-white' : ''}`} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user