add copy and create new note button in chat view

This commit is contained in:
duanfuxiang
2025-03-17 19:39:30 +08:00
parent 9488146162
commit b5766e50b2
6 changed files with 113 additions and 19 deletions

View File

@@ -868,7 +868,6 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
>
{message.content}
</ReactMarkdownItem>
{/* {message.content && <AssistantMessageActions key={"actions-" + message.id} message={message} />} */}
</div>
),
)}

View File

@@ -1,4 +1,4 @@
import { FolderOpen } from 'lucide-react'
import { FileSearch } from 'lucide-react'
import React from 'react'
import { useApp } from '../../contexts/AppContext'
@@ -44,7 +44,7 @@ export default function MarkdownRegexSearchFilesBlock({
>
<div className={'infio-chat-code-block-header'}>
<div className={'infio-chat-code-block-header-filename'}>
<FolderOpen size={14} className="infio-chat-code-block-header-icon" />
<FileSearch size={14} className="infio-chat-code-block-header-icon" />
<span>regex search files &quot;{regex}&quot; in {path}</span>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { FolderOpen } from 'lucide-react'
import { FileSearch } from 'lucide-react'
import React from 'react'
import { useApp } from '../../contexts/AppContext'
@@ -43,7 +43,7 @@ export default function MarkdownSemanticSearchFilesBlock({
>
<div className={'infio-chat-code-block-header'}>
<div className={'infio-chat-code-block-header-filename'}>
<FolderOpen size={14} className="infio-chat-code-block-header-icon" />
<FileSearch size={14} className="infio-chat-code-block-header-icon" />
<span>semantic search files &quot;{query}&quot; in {path}</span>
</div>
</div>

View File

@@ -1,16 +1,100 @@
import { CircleCheckBig, CircleHelp } from 'lucide-react';
import { ComponentPropsWithoutRef } from 'react';
import * as Tooltip from '@radix-ui/react-tooltip';
import { Check, CircleCheckBig, CircleHelp, CopyIcon, FilePlus2 } from 'lucide-react';
import { ComponentPropsWithoutRef, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import { useApp } from 'src/contexts/AppContext';
function CopyButton({ message }: { message: string }) {
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
await navigator.clipboard.writeText(message.trim())
setCopied(true)
setTimeout(() => {
setCopied(false)
}, 1500)
}
return (
<Tooltip.Provider delayDuration={0}>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button>
{copied ? (
<Check
size={12}
className="infio-chat-message-actions-icon--copied"
/>
) : (
<CopyIcon onClick={handleCopy} size={12} />
)}
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="infio-tooltip-content">
Copy message
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
)
}
function CreateNewFileButton({ message }: { message: string }) {
const app = useApp()
const [created, setCreated] = useState(false)
const handleCreate = async () => {
const firstLine = message.split('\n')[0].trim().replace(/[\\\/:]/g, '');
const filename = firstLine.slice(0, 200) + (firstLine.length > 200 ? '...' : '') || 'untitled';
console.log('filename', filename)
console.log('message', message)
await app.vault.create(`/${filename}.md`, message)
setCreated(true)
setTimeout(() => {
setCreated(false)
}, 1500)
}
return (
<Tooltip.Provider delayDuration={0}>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button style={{ color: '#008000' }}>
{created ? (
<Check
size={12}
className="infio-chat-message-actions-icon--copied"
/>
) : (
<FilePlus2 onClick={handleCreate} size={12} />
)}
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="infio-tooltip-content">
Create new note
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
)
}
const MarkdownWithIcons = ({ markdownContent, className }: { markdownContent: string, className?: string }) => {
// 预处理markdown内容将<icon>标签转换为ReactMarkdown可以处理的格式
const processedContent = markdownContent.replace(
/<icon\s+name=['"]([^'"]+)['"]\s+size=\{(\d+)\}(\s+className=['"]([^'"]+)['"])?[^>]*\/>/g,
(_, name, size, __, className) =>
(_, name, size, __, className) =>
`<span data-icon="${name}" data-size="${size}" ${className ? `class="${className}"` : ''}></span>`
);
const rawContent = markdownContent.replace(
/<icon\s+name=['"]([^'"]+)['"]\s+size=\{(\d+)\}(\s+className=['"]([^'"]+)['"])?[^>]*\/>/g,
() => ``
).trim();
const components = {
span: (props: ComponentPropsWithoutRef<'span'> & {
'data-icon'?: string;
@@ -20,7 +104,7 @@ const MarkdownWithIcons = ({ markdownContent, className }: { markdownContent: st
const name = props['data-icon'];
const size = props['data-size'] ? Number(props['data-size']) : 16;
const className = props.className || '';
switch (name) {
case 'ask_followup_question':
return <CircleHelp size={size} className={className} />;
@@ -35,13 +119,21 @@ const MarkdownWithIcons = ({ markdownContent, className }: { markdownContent: st
};
return (
<ReactMarkdown
className={`${className}`}
components={components}
rehypePlugins={[rehypeRaw]}
>
{processedContent}
</ReactMarkdown>
<>
<ReactMarkdown
className={`${className}`}
components={components}
rehypePlugins={[rehypeRaw]}
>
{processedContent}
</ReactMarkdown>
{processedContent &&
<div className="infio-chat-message-actions">
<CopyButton message={rawContent} />
<CreateNewFileButton message={rawContent} />
</div>}
</>
);
};