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>}
</>
);
};

View File

@ -10,7 +10,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. You will be informed on the work completed and what's remaining as you go.
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
4. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.
`
5. When referencing web pages, use Markdown-style links: [display text](url).`
}
function getObsidianObjectiveSection(): string {
@ -23,7 +23,10 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order.
2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. You will be informed on the work completed and what's remaining as you go.
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
4. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.`
4. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.
5. When referencing files, use Markdown-style links: [display text](file-path.md). Follow these rules:
- Always use full relative paths (e.g., [Daily/2024-04/26.md](Daily/2024-04/26.md)
- Never use bare filenames without links (e.g., "26.md")`
}
export function getObjectiveSection(mode: string): string {

View File

@ -16,7 +16,7 @@ import {
MentionableVault
} from '../types/mentionable'
import { InfioSettings } from '../types/settings'
import { Mode, defaultModeSlug, getFullModeDetails, getModeBySlug } from "../utils/modes"
import { Mode, getFullModeDetails } from "../utils/modes"
import {
readTFileContent